* Re: [PATCH v3 2/3] HID: quirks: Fix keyboard + touchpad on Lenovo Miix 630 for DT
From: Jeffrey Hugo @ 2019-04-18 14:43 UTC (permalink / raw)
To: Hans de Goede
Cc: Benjamin Tissoires, Rob Herring, Mark Rutland, Jiri Kosina,
Lee Jones, Bjorn Andersson, Dmitry Torokhov, agross, David Brown,
open list:HID CORE LAYER, devicetree, linux-arm-msm, lkml
In-Reply-To: <a47672ed-fc25-dd07-0ddf-2deca08283ef@redhat.com>
On Thu, Apr 18, 2019 at 3:51 AM Hans de Goede <hdegoede@redhat.com> wrote:
>
> Hi,
>
> On 18-04-19 11:34, Benjamin Tissoires wrote:
> > On Mon, Apr 15, 2019 at 6:11 PM Jeffrey Hugo <jeffrey.l.hugo@gmail.com> wrote:
> >>
> >> Following up on commit 2bafa1e96254 ("HID: quirks: Fix keyboard + touchpad
> >> on Lenovo Miix 630"), the devicetree (DT) identifier for the combo keyboard
> >> + touchpad device is "elan,combo400-i2c", which differs from the ACPI ID,
> >> thus if we want the quirk to work properly when booting via DT instead of
> >> ACPI, we need to key off the DT id as well.
> >>
> >> Signed-off-by: Jeffrey Hugo <jeffrey.l.hugo@gmail.com>
> >> ---
> >> drivers/hid/hid-quirks.c | 3 ++-
> >> 1 file changed, 2 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
> >> index 77ffba48cc73..00c08f8318b8 100644
> >> --- a/drivers/hid/hid-quirks.c
> >> +++ b/drivers/hid/hid-quirks.c
> >> @@ -997,7 +997,8 @@ bool hid_ignore(struct hid_device *hdev)
> >> return true;
> >> /* Same with product id 0x0400 */
> >> if (hdev->product == 0x0400 &&
> >> - strncmp(hdev->name, "QTEC0001", 8) != 0)
> >> + (strncmp(hdev->name, "QTEC0001", 8) != 0 ||
> >> + strncmp(hdev->name, "elan,combo400-i2c", 17) != 0))
> >
> > I think we are taking the problem the wrong way here.
> >
> > When I first introduced 6ccfe64, I thought 0x0400 would be reserved
> > for the elan_i2c touchpads only. But it turns out we are deliberately
> > disabling valid HID touchpads hoping that they would be picked up by
> > elan_i2c when elan_i2c has its own whitelist of devices.
> >
> > How about we turn this into list with the matching ones from elan_i2c:
> > if ((hdev->product == 0x0400 || hdev->product == 0x0401) &&
> > (strncmp(hdev->name, "ELAN0000", 8) == 0 ||
> > strncmp(hdev->name, "ELAN0100", 8) == 0 ||
> > ...
> > strncmp(hdev->name, "ELAN1000", 8) == 0))
> > return true;
> >
> > So next time we need to force binding a HID touchpad to elan_i2c, we
> > can just blacklist here and whitelist it in elan_i2c.
>
> This indeed sounds like a better way forward with this.
This sounds good to me. Let me implement it and test with the Miix
630. Thanks for the suggestion.
^ permalink raw reply
* Re: [PATCH v2] Input: uinput: Avoid Object-Already-Free with a global lock
From: dmitry.torokhov @ 2019-04-19 7:11 UTC (permalink / raw)
To: Mukesh Ojha
Cc: linux-input, linux-kernel, Gaurav Kohli, Peter Hutterer,
Martin Kepplinger, Paul E. McKenney
In-Reply-To: <a4d1a2f3-1db7-e300-9569-7b7a2fadd64e@codeaurora.org>
Hi Mukesh,
On Fri, Apr 19, 2019 at 12:17:44PM +0530, Mukesh Ojha wrote:
> For some reason my last mail did not get delivered, sending it again.
>
>
> On 4/18/2019 11:55 AM, Mukesh Ojha wrote:
> >
> >
> > On 4/18/2019 7:13 AM, dmitry.torokhov@gmail.com wrote:
> > > Hi Mukesh,
> > >
> > > On Mon, Apr 15, 2019 at 03:35:51PM +0530, Mukesh Ojha wrote:
> > > > Hi Dmitry,
> > > >
> > > > Can you please have a look at this patch ? as this seems to reproducing
> > > > quite frequently
> > > >
> > > > Thanks,
> > > > Mukesh
> > > >
> > > > On 4/10/2019 1:29 PM, Mukesh Ojha wrote:
> > > > > uinput_destroy_device() gets called from two places. In one place,
> > > > > uinput_ioctl_handler() where it is protected under a lock
> > > > > udev->mutex but there is no protection on udev device from freeing
> > > > > inside uinput_release().
> > > uinput_release() should be called when last file handle to the uinput
> > > instance is being dropped, so there should be no other users and thus we
> > > can't be racing with anyone.
> >
> > Lets say an example where i am creating input device quite frequently
> >
> > [ 97.836603] input: syz0 as /devices/virtual/input/input262
> > [ 97.845589] input: syz0 as /devices/virtual/input/input261
> > [ 97.849415] input: syz0 as /devices/virtual/input/input263
> > [ 97.856479] input: syz0 as /devices/virtual/input/input264
> > [ 97.936128] input: syz0 as /devices/virtual/input/input265
> >
> > e.g input265
> >
> > while input265 gets created [1] and handlers are getting registered on
> > that device*fput* gets called on
> > that device as user space got to know that input265 is created and its
> > reference is still 1(rare but possible).
Are you saying that there are 2 threads sharing the same file
descriptor, one issuing the registration ioctl while the other closing
the same fd?
Thanks.
--
Dmitry
^ permalink raw reply
* [git pull] Input updates for v5.1-rc5
From: Dmitry Torokhov @ 2019-04-19 7:31 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, linux-input
Hi Linus,
Please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
to receive updates for the input subsystem. You will get:
- several new key mappings for HID
- a host of new ACPI IDs used to identify Elan touchpads in Lenovo
laptops
Changelog:
---------
Anson Huang (1):
Input: snvs_pwrkey - initialize necessary driver data before enabling IRQ
Dmitry Torokhov (7):
Input: document meanings of KEY_SCREEN and KEY_ZOOM
[media] doc-rst: switch to new names for Full Screen/Aspect keys
HID: input: fix mapping of aspect ratio key
HID: input: add mapping for Expose/Overview key
HID: input: add mapping for keyboard Brightness Up/Down/Toggle keys
HID: input: add mapping for "Full Screen" key
HID: input: add mapping for "Toggle Display" key
KT Liao (1):
Input: elan_i2c - add hardware ID for multiple Lenovo laptops
Diffstat:
--------
Documentation/media/uapi/rc/rc-tables.rst | 4 ++--
drivers/hid/hid-input.c | 17 ++++++++++++++++-
drivers/input/keyboard/snvs_pwrkey.c | 6 +++---
drivers/input/mouse/elan_i2c_core.c | 25 +++++++++++++++++++++++++
include/uapi/linux/input-event-codes.h | 6 ++++--
5 files changed, 50 insertions(+), 8 deletions(-)
Thanks.
--
Dmitry
^ permalink raw reply
* Re: Input: synaptics-rmi4: fix possible double free
From: Dmitry Torokhov @ 2019-04-19 7:39 UTC (permalink / raw)
To: Pan Bian
Cc: Wei Yongjun, Nick Dyer, Nick Desaulniers, Kees Cook, linux-input,
linux-kernel
In-Reply-To: <1555552715-34820-1-git-send-email-bianpan2016@163.com>
On Thu, Apr 18, 2019 at 09:58:35AM +0800, Pan Bian wrote:
> The RMI4 function structure has been released in rmi_register_function
> if error occurs. However, it will be released again in the function
> rmi_create_function, which may result in a double-free bug.
>
> Signed-off-by: Pan Bian <bianpan2016@163.com>
Applied, thank you.
> ---
> drivers/input/rmi4/rmi_driver.c | 6 +-----
> 1 file changed, 1 insertion(+), 5 deletions(-)
>
> diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
> index fc3ab93..7fb358f 100644
> --- a/drivers/input/rmi4/rmi_driver.c
> +++ b/drivers/input/rmi4/rmi_driver.c
> @@ -860,7 +860,7 @@ static int rmi_create_function(struct rmi_device *rmi_dev,
>
> error = rmi_register_function(fn);
> if (error)
> - goto err_put_fn;
> + return error;
>
> if (pdt->function_number == 0x01)
> data->f01_container = fn;
> @@ -870,10 +870,6 @@ static int rmi_create_function(struct rmi_device *rmi_dev,
> list_add_tail(&fn->node, &data->function_list);
>
> return RMI_SCAN_CONTINUE;
> -
> -err_put_fn:
> - put_device(&fn->dev);
> - return error;
> }
>
> void rmi_enable_irq(struct rmi_device *rmi_dev, bool clear_wake)
> --
> 2.7.4
>
>
--
Dmitry
^ permalink raw reply
* Re: [PATCH 1/4] mod_devicetable: helper macro for declaring oftree module device table
From: Dmitry Torokhov @ 2019-04-19 7:40 UTC (permalink / raw)
To: Enrico Weigelt, metux IT consult; +Cc: linux-kernel, linux-input
In-Reply-To: <1555444645-15156-2-git-send-email-info@metux.net>
Hi Enrico,
On Tue, Apr 16, 2019 at 09:57:22PM +0200, Enrico Weigelt, metux IT consult wrote:
> Little helper macro that declares an oftree module device table,
> if CONFIG_OF is enabled. Otherwise it's just noop.
>
> This is also helpful if some drivers can be built w/ or w/o
> oftree support.
This should go to OF folks, please.
>
> Signed-off-by: Enrico Weigelt, metux IT consult <info@metux.net>
> ---
> include/linux/mod_devicetable.h | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
> index 448621c..853e301 100644
> --- a/include/linux/mod_devicetable.h
> +++ b/include/linux/mod_devicetable.h
> @@ -245,6 +245,15 @@ struct of_device_id {
> const void *data;
> };
>
> +/*
> + * macro for adding the of module device table only if CONFIG_OF enabled
> + */
> +#ifdef CONFIG_OF
> +#define MODULE_DEVICE_TABLE_OF(name) MODULE_DEVICE_TABLE(of,name)
> +#else
> +#define MODULE_DEVICE_TABLE_OF(name)
> +#endif /* CONFIG_OF */
> +
> /* VIO */
> struct vio_device_id {
> char type[32];
> --
> 1.9.1
>
--
Dmitry
^ permalink raw reply
* Re: [PATCH 3/4] input: keyboard: gpio_keys_polled: use gpio lookup table
From: Dmitry Torokhov @ 2019-04-19 7:48 UTC (permalink / raw)
To: Enrico Weigelt, metux IT consult; +Cc: linux-kernel, linux-input
In-Reply-To: <1555444645-15156-4-git-send-email-info@metux.net>
Hi Enrico,
On Tue, Apr 16, 2019 at 09:57:24PM +0200, Enrico Weigelt, metux IT consult wrote:
> Support the recently introduced gpio lookup tables for
> attaching to gpio lines. So, harcoded gpio numbers aren't
> needed anymore.
I would prefer if gpiod API could parse static board data/gpio lookup
tables for child nodes, instead of adding this to gpio-keys. Now that
Heikki Krogerus work on software nodes has landed I need to resurrect
my patch to gpiolib.
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH v7 0/2] Add Apple SPI keyboard and trackpad driver
From: Ronald Tschalär @ 2019-04-19 8:19 UTC (permalink / raw)
To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko, Andrzej Hajda,
Inki Dae, Greg Kroah-Hartman
Cc: Lukas Wunner, Federico Lorenzi, Laurent Pinchart, linux-input,
dri-devel, linux-kernel
This changeset adds a driver for the SPI keyboard and trackpad on recent
MacBook's and MacBook Pro's. The driver has seen a fair amount of use
over the last 2 years (basically anybody running linux on these
machines), with only relatively small changes in the last year or so.
For those interested, the driver development has been hosted at
https://github.com/cb22/macbook12-spi-driver/ (as well as my clone at
https://github.com/roadrunner2/macbook12-spi-driver/).
The first patch fixes a problem during config. While it affects the drm
tree, Andrzej Hajda has given his ok for this patch to be taken via the
input tree because the second patch here depends on it.
The second patch contains the new applespi driver.
Changes in v7:
- Fixed unused variable warning introduced in previous patch series and
accidently overlooked
Ronald Tschalär (2):
drm/bridge: sil_sii8620: make remote control optional.
Input: add Apple SPI keyboard and trackpad driver.
drivers/gpu/drm/bridge/Kconfig | 3 +-
drivers/gpu/drm/bridge/sil-sii8620.c | 10 +-
drivers/input/keyboard/Kconfig | 15 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/applespi.c | 1975 +++++++++++++++++++++++
drivers/input/keyboard/applespi.h | 29 +
drivers/input/keyboard/applespi_trace.h | 94 ++
7 files changed, 2122 insertions(+), 5 deletions(-)
create mode 100644 drivers/input/keyboard/applespi.c
create mode 100644 drivers/input/keyboard/applespi.h
create mode 100644 drivers/input/keyboard/applespi_trace.h
--
2.20.1
^ permalink raw reply
* [PATCH v7 1/2] drm/bridge: sil_sii8620: make remote control optional.
From: Ronald Tschalär @ 2019-04-19 8:19 UTC (permalink / raw)
To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko, Andrzej Hajda,
Inki Dae, Greg Kroah-Hartman
Cc: Lukas Wunner, Federico Lorenzi, Laurent Pinchart, linux-input,
dri-devel, linux-kernel
In-Reply-To: <20190419081926.13567-1-ronald@innovation.ch>
commit d6abe6df706c (drm/bridge: sil_sii8620: do not have a dependency
of RC_CORE) changed the driver to select both RC_CORE and INPUT.
However, this causes problems with other drivers, in particular an input
driver that depends on MFD_INTEL_LPSS_PCI (to be added in a separate
commit):
drivers/clk/Kconfig:9:error: recursive dependency detected!
drivers/clk/Kconfig:9: symbol COMMON_CLK is selected by MFD_INTEL_LPSS
drivers/mfd/Kconfig:566: symbol MFD_INTEL_LPSS is selected by MFD_INTEL_LPSS_PCI
drivers/mfd/Kconfig:580: symbol MFD_INTEL_LPSS_PCI is implied by KEYBOARD_APPLESPI
drivers/input/keyboard/Kconfig:73: symbol KEYBOARD_APPLESPI depends on INPUT
drivers/input/Kconfig:8: symbol INPUT is selected by DRM_SIL_SII8620
drivers/gpu/drm/bridge/Kconfig:83: symbol DRM_SIL_SII8620 depends on DRM_BRIDGE
drivers/gpu/drm/bridge/Kconfig:1: symbol DRM_BRIDGE is selected by DRM_PL111
drivers/gpu/drm/pl111/Kconfig:1: symbol DRM_PL111 depends on COMMON_CLK
According to the docs and general consensus, select should only be used
for non user-visible symbols, but both RC_CORE and INPUT are
user-visible. Furthermore almost all other references to INPUT
throughout the kernel config are depends, not selects. For this reason
the first part of this change reverts commit d6abe6df706c.
In order to address the original reason for commit d6abe6df706c, namely
that not all boards use the remote controller functionality and hence
should not need have to deal with RC_CORE, the second part of this
change now makes the remote control support in the driver optional and
contingent on RC_CORE being defined. And with this the hard dependency
on INPUT also goes away as that is only needed if RC_CORE is defined
(which in turn already depends on INPUT).
CC: Inki Dae <inki.dae@samsung.com>
CC: Andrzej Hajda <a.hajda@samsung.com>
CC: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
CC: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>
---
drivers/gpu/drm/bridge/Kconfig | 3 +--
drivers/gpu/drm/bridge/sil-sii8620.c | 10 +++++++---
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 2fee47b0d50b..9cf07105b73a 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -85,8 +85,7 @@ config DRM_SIL_SII8620
depends on OF
select DRM_KMS_HELPER
imply EXTCON
- select INPUT
- select RC_CORE
+ imply RC_CORE
help
Silicon Image SII8620 HDMI/MHL bridge chip driver.
diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
index a6e8f4591e63..cff3131aae6c 100644
--- a/drivers/gpu/drm/bridge/sil-sii8620.c
+++ b/drivers/gpu/drm/bridge/sil-sii8620.c
@@ -1763,10 +1763,8 @@ static bool sii8620_rcp_consume(struct sii8620 *ctx, u8 scancode)
scancode &= MHL_RCP_KEY_ID_MASK;
- if (!ctx->rc_dev) {
- dev_dbg(ctx->dev, "RCP input device not initialized\n");
+ if (!IS_ENABLED(CONFIG_RC_CORE) || !ctx->rc_dev)
return false;
- }
if (pressed)
rc_keydown(ctx->rc_dev, RC_PROTO_CEC, scancode, 0);
@@ -2103,6 +2101,9 @@ static void sii8620_init_rcp_input_dev(struct sii8620 *ctx)
struct rc_dev *rc_dev;
int ret;
+ if (!IS_ENABLED(CONFIG_RC_CORE))
+ return;
+
rc_dev = rc_allocate_device(RC_DRIVER_SCANCODE);
if (!rc_dev) {
dev_err(ctx->dev, "Failed to allocate RC device\n");
@@ -2217,6 +2218,9 @@ static void sii8620_detach(struct drm_bridge *bridge)
{
struct sii8620 *ctx = bridge_to_sii8620(bridge);
+ if (!IS_ENABLED(CONFIG_RC_CORE))
+ return;
+
rc_unregister_device(ctx->rc_dev);
}
--
2.20.1
^ permalink raw reply related
* [PATCH v7 2/2] Input: add Apple SPI keyboard and trackpad driver.
From: Ronald Tschalär @ 2019-04-19 8:19 UTC (permalink / raw)
To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko, Andrzej Hajda,
Inki Dae, Greg Kroah-Hartman
Cc: Lukas Wunner, Federico Lorenzi, Laurent Pinchart, linux-input,
dri-devel, linux-kernel
In-Reply-To: <20190419081926.13567-1-ronald@innovation.ch>
The keyboard and trackpad on recent MacBook's (since 8,1) and
MacBookPro's (13,* and 14,*) are attached to an SPI controller instead
of USB, as previously. The higher level protocol is not publicly
documented and hence has been reverse engineered. As a consequence there
are still a number of unknown fields and commands. However, the known
parts have been working well and received extensive testing and use.
In order for this driver to work, the proper SPI drivers need to be
loaded too; for MB8,1 these are spi_pxa2xx_platform and spi_pxa2xx_pci;
for all others they are spi_pxa2xx_platform and intel_lpss_pci. For this
reason enabling this driver in the config implies enabling the above
drivers.
CC: Federico Lorenzi <federico@travelground.com>
CC: Lukas Wunner <lukas@wunner.de>
CC: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=99891
Link: https://bugzilla.kernel.org/show_bug.cgi?id=108331
Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
drivers/input/keyboard/Kconfig | 15 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/applespi.c | 1975 +++++++++++++++++++++++
drivers/input/keyboard/applespi.h | 29 +
drivers/input/keyboard/applespi_trace.h | 94 ++
5 files changed, 2114 insertions(+)
create mode 100644 drivers/input/keyboard/applespi.c
create mode 100644 drivers/input/keyboard/applespi.h
create mode 100644 drivers/input/keyboard/applespi_trace.h
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index a878351f1643..d0a9e7fa2508 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -70,6 +70,21 @@ config KEYBOARD_AMIGA
config ATARI_KBD_CORE
bool
+config KEYBOARD_APPLESPI
+ tristate "Apple SPI keyboard and trackpad"
+ depends on ACPI && EFI
+ depends on SPI
+ depends on X86 || COMPILE_TEST
+ imply SPI_PXA2XX
+ imply SPI_PXA2XX_PCI
+ imply MFD_INTEL_LPSS_PCI
+ help
+ Say Y here if you are running Linux on any Apple MacBook8,1 or later,
+ or any MacBookPro13,* or MacBookPro14,*.
+
+ To compile this driver as a module, choose M here: the
+ module will be called applespi.
+
config KEYBOARD_ATARI
tristate "Atari keyboard"
depends on ATARI
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 182e92985dbf..9283fee2505a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_KEYBOARD_ADP5520) += adp5520-keys.o
obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o
obj-$(CONFIG_KEYBOARD_ADP5589) += adp5589-keys.o
obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o
+obj-$(CONFIG_KEYBOARD_APPLESPI) += applespi.o
obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o
obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o
obj-$(CONFIG_KEYBOARD_BCM) += bcm-keypad.o
diff --git a/drivers/input/keyboard/applespi.c b/drivers/input/keyboard/applespi.c
new file mode 100644
index 000000000000..c23d7797d108
--- /dev/null
+++ b/drivers/input/keyboard/applespi.c
@@ -0,0 +1,1975 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MacBook (Pro) SPI keyboard and touchpad driver
+ *
+ * Copyright (c) 2015-2018 Federico Lorenzi
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ */
+
+/*
+ * The keyboard and touchpad controller on the MacBookAir6, MacBookPro12,
+ * MacBook8 and newer can be driven either by USB or SPI. However the USB
+ * pins are only connected on the MacBookAir6 and 7 and the MacBookPro12.
+ * All others need this driver. The interface is selected using ACPI methods:
+ *
+ * * UIEN ("USB Interface Enable"): If invoked with argument 1, disables SPI
+ * and enables USB. If invoked with argument 0, disables USB.
+ * * UIST ("USB Interface Status"): Returns 1 if USB is enabled, 0 otherwise.
+ * * SIEN ("SPI Interface Enable"): If invoked with argument 1, disables USB
+ * and enables SPI. If invoked with argument 0, disables SPI.
+ * * SIST ("SPI Interface Status"): Returns 1 if SPI is enabled, 0 otherwise.
+ * * ISOL: Resets the four GPIO pins used for SPI. Intended to be invoked with
+ * argument 1, then once more with argument 0.
+ *
+ * UIEN and UIST are only provided on models where the USB pins are connected.
+ *
+ * SPI-based Protocol
+ * ------------------
+ *
+ * The device and driver exchange messages (struct message); each message is
+ * encapsulated in one or more packets (struct spi_packet). There are two types
+ * of exchanges: reads, and writes. A read is signaled by a GPE, upon which one
+ * message can be read from the device. A write exchange consists of writing a
+ * command message, immediately reading a short status packet, and then, upon
+ * receiving a GPE, reading the response message. Write exchanges cannot be
+ * interleaved, i.e. a new write exchange must not be started till the previous
+ * write exchange is complete. Whether a received message is part of a read or
+ * write exchange is indicated in the encapsulating packet's flags field.
+ *
+ * A single message may be too large to fit in a single packet (which has a
+ * fixed, 256-byte size). In that case it will be split over multiple,
+ * consecutive packets.
+ */
+
+#include <linux/acpi.h>
+#include <linux/crc16.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/efi.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/spi/spi.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include <asm/barrier.h>
+#include <asm/unaligned.h>
+
+#define CREATE_TRACE_POINTS
+#include "applespi.h"
+#include "applespi_trace.h"
+
+#define APPLESPI_PACKET_SIZE 256
+#define APPLESPI_STATUS_SIZE 4
+
+#define PACKET_TYPE_READ 0x20
+#define PACKET_TYPE_WRITE 0x40
+#define PACKET_DEV_KEYB 0x01
+#define PACKET_DEV_TPAD 0x02
+#define PACKET_DEV_INFO 0xd0
+
+#define MAX_ROLLOVER 6
+
+#define MAX_FINGERS 11
+#define MAX_FINGER_ORIENTATION 16384
+#define MAX_PKTS_PER_MSG 2
+
+#define KBD_BL_LEVEL_MIN 32U
+#define KBD_BL_LEVEL_MAX 255U
+#define KBD_BL_LEVEL_SCALE 1000000U
+#define KBD_BL_LEVEL_ADJ \
+ ((KBD_BL_LEVEL_MAX - KBD_BL_LEVEL_MIN) * KBD_BL_LEVEL_SCALE / 255U)
+
+#define EFI_BL_LEVEL_NAME L"KeyboardBacklightLevel"
+#define EFI_BL_LEVEL_GUID EFI_GUID(0xa076d2af, 0x9678, 0x4386, 0x8b, 0x58, 0x1f, 0xc8, 0xef, 0x04, 0x16, 0x19)
+
+#define APPLE_FLAG_FKEY 0x01
+
+#define SPI_RW_CHG_DELAY_US 100 /* from experimentation, in µs */
+
+#define SYNAPTICS_VENDOR_ID 0x06cb
+
+static unsigned int fnmode = 1;
+module_param(fnmode, uint, 0644);
+MODULE_PARM_DESC(fnmode, "Mode of Fn key on Apple keyboards (0 = disabled, [1] = fkeyslast, 2 = fkeysfirst)");
+
+static unsigned int fnremap;
+module_param(fnremap, uint, 0644);
+MODULE_PARM_DESC(fnremap, "Remap Fn key ([0] = no-remap; 1 = left-ctrl, 2 = left-shift, 3 = left-alt, 4 = left-meta, 6 = right-shift, 7 = right-alt, 8 = right-meta)");
+
+static bool iso_layout;
+module_param(iso_layout, bool, 0644);
+MODULE_PARM_DESC(iso_layout, "Enable/Disable hardcoded ISO-layout of the keyboard. ([0] = disabled, 1 = enabled)");
+
+static char touchpad_dimensions[40];
+module_param_string(touchpad_dimensions, touchpad_dimensions,
+ sizeof(touchpad_dimensions), 0444);
+MODULE_PARM_DESC(touchpad_dimensions, "The pixel dimensions of the touchpad, as XxY+W+H .");
+
+/**
+ * struct keyboard_protocol - keyboard message.
+ * message.type = 0x0110, message.length = 0x000a
+ *
+ * @unknown1: unknown
+ * @modifiers: bit-set of modifier/control keys pressed
+ * @unknown2: unknown
+ * @keys_pressed: the (non-modifier) keys currently pressed
+ * @fn_pressed: whether the fn key is currently pressed
+ * @crc16: crc over the whole message struct (message header +
+ * this struct) minus this @crc16 field
+ */
+struct keyboard_protocol {
+ __u8 unknown1;
+ __u8 modifiers;
+ __u8 unknown2;
+ __u8 keys_pressed[MAX_ROLLOVER];
+ __u8 fn_pressed;
+ __le16 crc16;
+};
+
+/**
+ * struct tp_finger - single trackpad finger structure, le16-aligned
+ *
+ * @origin: zero when switching track finger
+ * @abs_x: absolute x coodinate
+ * @abs_y: absolute y coodinate
+ * @rel_x: relative x coodinate
+ * @rel_y: relative y coodinate
+ * @tool_major: tool area, major axis
+ * @tool_minor: tool area, minor axis
+ * @orientation: 16384 when point, else 15 bit angle
+ * @touch_major: touch area, major axis
+ * @touch_minor: touch area, minor axis
+ * @unused: zeros
+ * @pressure: pressure on forcetouch touchpad
+ * @multi: one finger: varies, more fingers: constant
+ * @crc16: on last finger: crc over the whole message struct
+ * (i.e. message header + this struct) minus the last
+ * @crc16 field; unknown on all other fingers.
+ */
+struct tp_finger {
+ __le16 origin;
+ __le16 abs_x;
+ __le16 abs_y;
+ __le16 rel_x;
+ __le16 rel_y;
+ __le16 tool_major;
+ __le16 tool_minor;
+ __le16 orientation;
+ __le16 touch_major;
+ __le16 touch_minor;
+ __le16 unused[2];
+ __le16 pressure;
+ __le16 multi;
+ __le16 crc16;
+};
+
+/**
+ * struct touchpad_protocol - touchpad message.
+ * message.type = 0x0210
+ *
+ * @unknown1: unknown
+ * @clicked: 1 if a button-click was detected, 0 otherwise
+ * @unknown2: unknown
+ * @number_of_fingers: the number of fingers being reported in @fingers
+ * @clicked2: same as @clicked
+ * @unknown3: unknown
+ * @fingers: the data for each finger
+ */
+struct touchpad_protocol {
+ __u8 unknown1[1];
+ __u8 clicked;
+ __u8 unknown2[28];
+ __u8 number_of_fingers;
+ __u8 clicked2;
+ __u8 unknown3[16];
+ struct tp_finger fingers[0];
+};
+
+/**
+ * struct command_protocol_tp_info - get touchpad info.
+ * message.type = 0x1020, message.length = 0x0000
+ *
+ * @crc16: crc over the whole message struct (message header +
+ * this struct) minus this @crc16 field
+ */
+struct command_protocol_tp_info {
+ __le16 crc16;
+};
+
+/**
+ * struct touchpad_info - touchpad info response.
+ * message.type = 0x1020, message.length = 0x006e
+ *
+ * @unknown1: unknown
+ * @model_flags: flags (vary by model number, but significance otherwise
+ * unknown)
+ * @model_no: the touchpad model number
+ * @unknown2: unknown
+ * @crc16: crc over the whole message struct (message header +
+ * this struct) minus this @crc16 field
+ */
+struct touchpad_info_protocol {
+ __u8 unknown1[105];
+ __u8 model_flags;
+ __u8 model_no;
+ __u8 unknown2[3];
+ __le16 crc16;
+};
+
+/**
+ * struct command_protocol_mt_init - initialize multitouch.
+ * message.type = 0x0252, message.length = 0x0002
+ *
+ * @cmd: value: 0x0102
+ * @crc16: crc over the whole message struct (message header +
+ * this struct) minus this @crc16 field
+ */
+struct command_protocol_mt_init {
+ __le16 cmd;
+ __le16 crc16;
+};
+
+/**
+ * struct command_protocol_capsl - toggle caps-lock led
+ * message.type = 0x0151, message.length = 0x0002
+ *
+ * @unknown: value: 0x01 (length?)
+ * @led: 0 off, 2 on
+ * @crc16: crc over the whole message struct (message header +
+ * this struct) minus this @crc16 field
+ */
+struct command_protocol_capsl {
+ __u8 unknown;
+ __u8 led;
+ __le16 crc16;
+};
+
+/**
+ * struct command_protocol_bl - set keyboard backlight brightness
+ * message.type = 0xB051, message.length = 0x0006
+ *
+ * @const1: value: 0x01B0
+ * @level: the brightness level to set
+ * @const2: value: 0x0001 (backlight off), 0x01F4 (backlight on)
+ * @crc16: crc over the whole message struct (message header +
+ * this struct) minus this @crc16 field
+ */
+struct command_protocol_bl {
+ __le16 const1;
+ __le16 level;
+ __le16 const2;
+ __le16 crc16;
+};
+
+/**
+ * struct message - a complete spi message.
+ *
+ * Each message begins with fixed header, followed by a message-type specific
+ * payload, and ends with a 16-bit crc. Because of the varying lengths of the
+ * payload, the crc is defined at the end of each payload struct, rather than
+ * in this struct.
+ *
+ * @type: the message type
+ * @zero: always 0
+ * @counter: incremented on each message, rolls over after 255; there is a
+ * separate counter for each message type.
+ * @rsp_buf_len:response buffer length (the exact nature of this field is quite
+ * speculative). On a request/write this is often the same as
+ * @length, though in some cases it has been seen to be much larger
+ * (e.g. 0x400); on a response/read this the same as on the
+ * request; for reads that are not responses it is 0.
+ * @length: length of the remainder of the data in the whole message
+ * structure (after re-assembly in case of being split over
+ * multiple spi-packets), minus the trailing crc. The total size
+ * of the message struct is therefore @length + 10.
+ */
+struct message {
+ __le16 type;
+ __u8 zero;
+ __u8 counter;
+ __le16 rsp_buf_len;
+ __le16 length;
+ union {
+ struct keyboard_protocol keyboard;
+ struct touchpad_protocol touchpad;
+ struct touchpad_info_protocol tp_info;
+ struct command_protocol_tp_info tp_info_command;
+ struct command_protocol_mt_init init_mt_command;
+ struct command_protocol_capsl capsl_command;
+ struct command_protocol_bl bl_command;
+ __u8 data[0];
+ };
+};
+
+/* type + zero + counter + rsp_buf_len + length */
+#define MSG_HEADER_SIZE 8
+
+/**
+ * struct spi_packet - a complete spi packet; always 256 bytes. This carries
+ * the (parts of the) message in the data. But note that this does not
+ * necessarily contain a complete message, as in some cases (e.g. many
+ * fingers pressed) the message is split over multiple packets (see the
+ * @offset, @remaining, and @length fields). In general the data parts in
+ * spi_packet's are concatenated until @remaining is 0, and the result is an
+ * message.
+ *
+ * @flags: 0x40 = write (to device), 0x20 = read (from device); note that
+ * the response to a write still has 0x40.
+ * @device: 1 = keyboard, 2 = touchpad
+ * @offset: specifies the offset of this packet's data in the complete
+ * message; i.e. > 0 indicates this is a continuation packet (in
+ * the second packet for a message split over multiple packets
+ * this would then be the same as the @length in the first packet)
+ * @remaining: number of message bytes remaining in subsequents packets (in
+ * the first packet of a message split over two packets this would
+ * then be the same as the @length in the second packet)
+ * @length: length of the valid data in the @data in this packet
+ * @data: all or part of a message
+ * @crc16: crc over this whole structure minus this @crc16 field. This
+ * covers just this packet, even on multi-packet messages (in
+ * contrast to the crc in the message).
+ */
+struct spi_packet {
+ __u8 flags;
+ __u8 device;
+ __le16 offset;
+ __le16 remaining;
+ __le16 length;
+ __u8 data[246];
+ __le16 crc16;
+};
+
+struct spi_settings {
+ u64 spi_cs_delay; /* cs-to-clk delay in us */
+ u64 reset_a2r_usec; /* active-to-receive delay? */
+ u64 reset_rec_usec; /* ? (cur val: 10) */
+};
+
+/* this mimics struct drm_rect */
+struct applespi_tp_info {
+ int x_min;
+ int y_min;
+ int x_max;
+ int y_max;
+};
+
+struct applespi_data {
+ struct spi_device *spi;
+ struct spi_settings spi_settings;
+ struct input_dev *keyboard_input_dev;
+ struct input_dev *touchpad_input_dev;
+
+ u8 *tx_buffer;
+ u8 *tx_status;
+ u8 *rx_buffer;
+
+ u8 *msg_buf;
+ unsigned int saved_msg_len;
+
+ struct applespi_tp_info tp_info;
+
+ u8 last_keys_pressed[MAX_ROLLOVER];
+ u8 last_keys_fn_pressed[MAX_ROLLOVER];
+ u8 last_fn_pressed;
+ struct input_mt_pos pos[MAX_FINGERS];
+ int slots[MAX_FINGERS];
+ int gpe;
+ acpi_handle sien;
+ acpi_handle sist;
+
+ struct spi_transfer dl_t;
+ struct spi_transfer rd_t;
+ struct spi_message rd_m;
+
+ struct spi_transfer ww_t;
+ struct spi_transfer wd_t;
+ struct spi_transfer wr_t;
+ struct spi_transfer st_t;
+ struct spi_message wr_m;
+
+ bool want_tp_info_cmd;
+ bool want_mt_init_cmd;
+ bool want_cl_led_on;
+ bool have_cl_led_on;
+ unsigned int want_bl_level;
+ unsigned int have_bl_level;
+ unsigned int cmd_msg_cntr;
+ /* lock to protect the above parameters and flags below */
+ spinlock_t cmd_msg_lock;
+ bool cmd_msg_queued;
+ enum applespi_evt_type cmd_evt_type;
+
+ struct led_classdev backlight_info;
+
+ bool suspended;
+ bool drain;
+ wait_queue_head_t drain_complete;
+ bool read_active;
+ bool write_active;
+
+ struct work_struct work;
+ struct touchpad_info_protocol rcvd_tp_info;
+
+ struct dentry *debugfs_root;
+ bool debug_tp_dim;
+ char tp_dim_val[40];
+ int tp_dim_min_x;
+ int tp_dim_max_x;
+ int tp_dim_min_y;
+ int tp_dim_max_y;
+};
+
+static const unsigned char applespi_scancodes[] = {
+ 0, 0, 0, 0,
+ KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J,
+ KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T,
+ KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z,
+ KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0,
+ KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB, KEY_SPACE, KEY_MINUS,
+ KEY_EQUAL, KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH, 0,
+ KEY_SEMICOLON, KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT, KEY_SLASH,
+ KEY_CAPSLOCK,
+ KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9,
+ KEY_F10, KEY_F11, KEY_F12, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ KEY_RIGHT, KEY_LEFT, KEY_DOWN, KEY_UP,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_102ND,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RO, 0, KEY_YEN, 0, 0, 0, 0, 0,
+ 0, KEY_KATAKANAHIRAGANA, KEY_MUHENKAN
+};
+
+/*
+ * This must have exactly as many entries as there are bits in
+ * struct keyboard_protocol.modifiers .
+ */
+static const unsigned char applespi_controlcodes[] = {
+ KEY_LEFTCTRL,
+ KEY_LEFTSHIFT,
+ KEY_LEFTALT,
+ KEY_LEFTMETA,
+ 0,
+ KEY_RIGHTSHIFT,
+ KEY_RIGHTALT,
+ KEY_RIGHTMETA
+};
+
+struct applespi_key_translation {
+ u16 from;
+ u16 to;
+ u8 flags;
+};
+
+static const struct applespi_key_translation applespi_fn_codes[] = {
+ { KEY_BACKSPACE, KEY_DELETE },
+ { KEY_ENTER, KEY_INSERT },
+ { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+ { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY },
+ { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY },
+ { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY },
+ { KEY_F5, KEY_KBDILLUMDOWN, APPLE_FLAG_FKEY },
+ { KEY_F6, KEY_KBDILLUMUP, APPLE_FLAG_FKEY },
+ { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY },
+ { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY },
+ { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY },
+ { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY },
+ { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY },
+ { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY },
+ { KEY_RIGHT, KEY_END },
+ { KEY_LEFT, KEY_HOME },
+ { KEY_DOWN, KEY_PAGEDOWN },
+ { KEY_UP, KEY_PAGEUP },
+ { }
+};
+
+static const struct applespi_key_translation apple_iso_keyboard[] = {
+ { KEY_GRAVE, KEY_102ND },
+ { KEY_102ND, KEY_GRAVE },
+ { }
+};
+
+struct applespi_tp_model_info {
+ u16 model;
+ struct applespi_tp_info tp_info;
+};
+
+static const struct applespi_tp_model_info applespi_tp_models[] = {
+ {
+ .model = 0x04, /* MB8 MB9 MB10 */
+ .tp_info = { -5087, -182, 5579, 6089 },
+ },
+ {
+ .model = 0x05, /* MBP13,1 MBP13,2 MBP14,1 MBP14,2 */
+ .tp_info = { -6243, -170, 6749, 7685 },
+ },
+ {
+ .model = 0x06, /* MBP13,3 MBP14,3 */
+ .tp_info = { -7456, -163, 7976, 9283 },
+ },
+ {}
+};
+
+typedef void (*applespi_trace_fun)(enum applespi_evt_type,
+ enum applespi_pkt_type, u8 *, size_t);
+
+static applespi_trace_fun applespi_get_trace_fun(enum applespi_evt_type type)
+{
+ switch (type) {
+ case ET_CMD_TP_INI:
+ return trace_applespi_tp_ini_cmd;
+ case ET_CMD_BL:
+ return trace_applespi_backlight_cmd;
+ case ET_CMD_CL:
+ return trace_applespi_caps_lock_cmd;
+ case ET_RD_KEYB:
+ return trace_applespi_keyboard_data;
+ case ET_RD_TPAD:
+ return trace_applespi_touchpad_data;
+ case ET_RD_UNKN:
+ return trace_applespi_unknown_data;
+ default:
+ WARN_ONCE(1, "Unknown msg type %d", type);
+ return trace_applespi_unknown_data;
+ }
+}
+
+static void applespi_setup_read_txfrs(struct applespi_data *applespi)
+{
+ struct spi_message *msg = &applespi->rd_m;
+ struct spi_transfer *dl_t = &applespi->dl_t;
+ struct spi_transfer *rd_t = &applespi->rd_t;
+
+ memset(dl_t, 0, sizeof(*dl_t));
+ memset(rd_t, 0, sizeof(*rd_t));
+
+ dl_t->delay_usecs = applespi->spi_settings.spi_cs_delay;
+
+ rd_t->rx_buf = applespi->rx_buffer;
+ rd_t->len = APPLESPI_PACKET_SIZE;
+
+ spi_message_init(msg);
+ spi_message_add_tail(dl_t, msg);
+ spi_message_add_tail(rd_t, msg);
+}
+
+static void applespi_setup_write_txfrs(struct applespi_data *applespi)
+{
+ struct spi_message *msg = &applespi->wr_m;
+ struct spi_transfer *wt_t = &applespi->ww_t;
+ struct spi_transfer *dl_t = &applespi->wd_t;
+ struct spi_transfer *wr_t = &applespi->wr_t;
+ struct spi_transfer *st_t = &applespi->st_t;
+
+ memset(wt_t, 0, sizeof(*wt_t));
+ memset(dl_t, 0, sizeof(*dl_t));
+ memset(wr_t, 0, sizeof(*wr_t));
+ memset(st_t, 0, sizeof(*st_t));
+
+ /*
+ * All we need here is a delay at the beginning of the message before
+ * asserting cs. But the current spi API doesn't support this, so we
+ * end up with an extra unnecessary (but harmless) cs assertion and
+ * deassertion.
+ */
+ wt_t->delay_usecs = SPI_RW_CHG_DELAY_US;
+ wt_t->cs_change = 1;
+
+ dl_t->delay_usecs = applespi->spi_settings.spi_cs_delay;
+
+ wr_t->tx_buf = applespi->tx_buffer;
+ wr_t->len = APPLESPI_PACKET_SIZE;
+ wr_t->delay_usecs = SPI_RW_CHG_DELAY_US;
+
+ st_t->rx_buf = applespi->tx_status;
+ st_t->len = APPLESPI_STATUS_SIZE;
+
+ spi_message_init(msg);
+ spi_message_add_tail(wt_t, msg);
+ spi_message_add_tail(dl_t, msg);
+ spi_message_add_tail(wr_t, msg);
+ spi_message_add_tail(st_t, msg);
+}
+
+static int applespi_async(struct applespi_data *applespi,
+ struct spi_message *message, void (*complete)(void *))
+{
+ message->complete = complete;
+ message->context = applespi;
+
+ return spi_async(applespi->spi, message);
+}
+
+static inline bool applespi_check_write_status(struct applespi_data *applespi,
+ int sts)
+{
+ static u8 status_ok[] = { 0xac, 0x27, 0x68, 0xd5 };
+
+ if (sts < 0) {
+ dev_warn(&applespi->spi->dev, "Error writing to device: %d\n",
+ sts);
+ return false;
+ }
+
+ if (memcmp(applespi->tx_status, status_ok, APPLESPI_STATUS_SIZE)) {
+ dev_warn(&applespi->spi->dev, "Error writing to device: %*ph\n",
+ APPLESPI_STATUS_SIZE, applespi->tx_status);
+ return false;
+ }
+
+ return true;
+}
+
+static int applespi_get_spi_settings(struct applespi_data *applespi)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&applespi->spi->dev);
+ const union acpi_object *o;
+ struct spi_settings *settings = &applespi->spi_settings;
+
+ if (!acpi_dev_get_property(adev, "spiCSDelay", ACPI_TYPE_BUFFER, &o))
+ settings->spi_cs_delay = *(u64 *)o->buffer.pointer;
+ else
+ dev_warn(&applespi->spi->dev,
+ "Property spiCSDelay not found\n");
+
+ if (!acpi_dev_get_property(adev, "resetA2RUsec", ACPI_TYPE_BUFFER, &o))
+ settings->reset_a2r_usec = *(u64 *)o->buffer.pointer;
+ else
+ dev_warn(&applespi->spi->dev,
+ "Property resetA2RUsec not found\n");
+
+ if (!acpi_dev_get_property(adev, "resetRecUsec", ACPI_TYPE_BUFFER, &o))
+ settings->reset_rec_usec = *(u64 *)o->buffer.pointer;
+ else
+ dev_warn(&applespi->spi->dev,
+ "Property resetRecUsec not found\n");
+
+ dev_dbg(&applespi->spi->dev,
+ "SPI settings: spi_cs_delay=%llu reset_a2r_usec=%llu reset_rec_usec=%llu\n",
+ settings->spi_cs_delay, settings->reset_a2r_usec,
+ settings->reset_rec_usec);
+
+ return 0;
+}
+
+static int applespi_setup_spi(struct applespi_data *applespi)
+{
+ int sts;
+
+ sts = applespi_get_spi_settings(applespi);
+ if (sts)
+ return sts;
+
+ spin_lock_init(&applespi->cmd_msg_lock);
+ init_waitqueue_head(&applespi->drain_complete);
+
+ return 0;
+}
+
+static int applespi_enable_spi(struct applespi_data *applespi)
+{
+ acpi_status acpi_sts;
+ unsigned long long spi_status;
+
+ /* check if SPI is already enabled, so we can skip the delay below */
+ acpi_sts = acpi_evaluate_integer(applespi->sist, NULL, NULL,
+ &spi_status);
+ if (ACPI_SUCCESS(acpi_sts) && spi_status)
+ return 0;
+
+ /* SIEN(1) will enable SPI communication */
+ acpi_sts = acpi_execute_simple_method(applespi->sien, NULL, 1);
+ if (ACPI_FAILURE(acpi_sts)) {
+ dev_err(&applespi->spi->dev, "SIEN failed: %s\n",
+ acpi_format_exception(acpi_sts));
+ return -ENODEV;
+ }
+
+ /*
+ * Allow the SPI interface to come up before returning. Without this
+ * delay, the SPI commands to enable multitouch mode may not reach
+ * the trackpad controller, causing pointer movement to break upon
+ * resume from sleep.
+ */
+ msleep(50);
+
+ return 0;
+}
+
+static int applespi_send_cmd_msg(struct applespi_data *applespi);
+
+static void applespi_msg_complete(struct applespi_data *applespi,
+ bool is_write_msg, bool is_read_compl)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ if (is_read_compl)
+ applespi->read_active = false;
+ if (is_write_msg)
+ applespi->write_active = false;
+
+ if (applespi->drain && !applespi->write_active)
+ wake_up_all(&applespi->drain_complete);
+
+ if (is_write_msg) {
+ applespi->cmd_msg_queued = false;
+ applespi_send_cmd_msg(applespi);
+ }
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+}
+
+static void applespi_async_write_complete(void *context)
+{
+ struct applespi_data *applespi = context;
+ enum applespi_evt_type evt_type = applespi->cmd_evt_type;
+
+ applespi_get_trace_fun(evt_type)(evt_type, PT_WRITE,
+ applespi->tx_buffer,
+ APPLESPI_PACKET_SIZE);
+ applespi_get_trace_fun(evt_type)(evt_type, PT_STATUS,
+ applespi->tx_status,
+ APPLESPI_STATUS_SIZE);
+
+ if (!applespi_check_write_status(applespi, applespi->wr_m.status)) {
+ /*
+ * If we got an error, we presumably won't get the expected
+ * response message either.
+ */
+ applespi_msg_complete(applespi, true, false);
+ }
+}
+
+static int applespi_send_cmd_msg(struct applespi_data *applespi)
+{
+ u16 crc;
+ int sts;
+ struct spi_packet *packet = (struct spi_packet *)applespi->tx_buffer;
+ struct message *message = (struct message *)packet->data;
+ u16 msg_len;
+ u8 device;
+
+ /* check if draining */
+ if (applespi->drain)
+ return 0;
+
+ /* check whether send is in progress */
+ if (applespi->cmd_msg_queued)
+ return 0;
+
+ /* set up packet */
+ memset(packet, 0, APPLESPI_PACKET_SIZE);
+
+ /* are we processing init commands? */
+ if (applespi->want_tp_info_cmd) {
+ applespi->want_tp_info_cmd = false;
+ applespi->want_mt_init_cmd = true;
+ applespi->cmd_evt_type = ET_CMD_TP_INI;
+
+ /* build init command */
+ device = PACKET_DEV_INFO;
+
+ message->type = cpu_to_le16(0x1020);
+ msg_len = sizeof(message->tp_info_command);
+
+ message->zero = 0x02;
+ message->rsp_buf_len = cpu_to_le16(0x0200);
+
+ } else if (applespi->want_mt_init_cmd) {
+ applespi->want_mt_init_cmd = false;
+ applespi->cmd_evt_type = ET_CMD_TP_INI;
+
+ /* build init command */
+ device = PACKET_DEV_TPAD;
+
+ message->type = cpu_to_le16(0x0252);
+ msg_len = sizeof(message->init_mt_command);
+
+ message->init_mt_command.cmd = cpu_to_le16(0x0102);
+
+ /* do we need caps-lock command? */
+ } else if (applespi->want_cl_led_on != applespi->have_cl_led_on) {
+ applespi->have_cl_led_on = applespi->want_cl_led_on;
+ applespi->cmd_evt_type = ET_CMD_CL;
+
+ /* build led command */
+ device = PACKET_DEV_KEYB;
+
+ message->type = cpu_to_le16(0x0151);
+ msg_len = sizeof(message->capsl_command);
+
+ message->capsl_command.unknown = 0x01;
+ message->capsl_command.led = applespi->have_cl_led_on ? 2 : 0;
+
+ /* do we need backlight command? */
+ } else if (applespi->want_bl_level != applespi->have_bl_level) {
+ applespi->have_bl_level = applespi->want_bl_level;
+ applespi->cmd_evt_type = ET_CMD_BL;
+
+ /* build command buffer */
+ device = PACKET_DEV_KEYB;
+
+ message->type = cpu_to_le16(0xB051);
+ msg_len = sizeof(message->bl_command);
+
+ message->bl_command.const1 = cpu_to_le16(0x01B0);
+ message->bl_command.level =
+ cpu_to_le16(applespi->have_bl_level);
+
+ if (applespi->have_bl_level > 0)
+ message->bl_command.const2 = cpu_to_le16(0x01F4);
+ else
+ message->bl_command.const2 = cpu_to_le16(0x0001);
+
+ /* everything's up-to-date */
+ } else {
+ return 0;
+ }
+
+ /* finalize packet */
+ packet->flags = PACKET_TYPE_WRITE;
+ packet->device = device;
+ packet->length = cpu_to_le16(MSG_HEADER_SIZE + msg_len);
+
+ message->counter = applespi->cmd_msg_cntr++ % (U8_MAX + 1);
+
+ message->length = cpu_to_le16(msg_len - 2);
+ if (!message->rsp_buf_len)
+ message->rsp_buf_len = message->length;
+
+ crc = crc16(0, (u8 *)message, le16_to_cpu(packet->length) - 2);
+ put_unaligned_le16(crc, &message->data[msg_len - 2]);
+
+ crc = crc16(0, (u8 *)packet, sizeof(*packet) - 2);
+ packet->crc16 = cpu_to_le16(crc);
+
+ /* send command */
+ sts = applespi_async(applespi, &applespi->wr_m,
+ applespi_async_write_complete);
+ if (sts) {
+ dev_warn(&applespi->spi->dev,
+ "Error queueing async write to device: %d\n", sts);
+ return sts;
+ }
+
+ applespi->cmd_msg_queued = true;
+ applespi->write_active = true;
+
+ return 0;
+}
+
+static void applespi_init(struct applespi_data *applespi, bool is_resume)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ if (is_resume)
+ applespi->want_mt_init_cmd = true;
+ else
+ applespi->want_tp_info_cmd = true;
+ applespi_send_cmd_msg(applespi);
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+}
+
+static int applespi_set_capsl_led(struct applespi_data *applespi,
+ bool capslock_on)
+{
+ unsigned long flags;
+ int sts;
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ applespi->want_cl_led_on = capslock_on;
+ sts = applespi_send_cmd_msg(applespi);
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+
+ return sts;
+}
+
+static void applespi_set_bl_level(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct applespi_data *applespi =
+ container_of(led_cdev, struct applespi_data, backlight_info);
+ unsigned long flags;
+ int sts;
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ if (value == 0) {
+ applespi->want_bl_level = value;
+ } else {
+ /*
+ * The backlight does not turn on till level 32, so we scale
+ * the range here so that from a user's perspective it turns
+ * on at 1.
+ */
+ applespi->want_bl_level =
+ ((value * KBD_BL_LEVEL_ADJ) / KBD_BL_LEVEL_SCALE +
+ KBD_BL_LEVEL_MIN);
+ }
+
+ sts = applespi_send_cmd_msg(applespi);
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+}
+
+static int applespi_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value)
+{
+ struct applespi_data *applespi = input_get_drvdata(dev);
+
+ switch (type) {
+ case EV_LED:
+ applespi_set_capsl_led(applespi, !!test_bit(LED_CAPSL, dev->led));
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* lifted from the BCM5974 driver and renamed from raw2int */
+/* convert 16-bit little endian to signed integer */
+static inline int le16_to_int(__le16 x)
+{
+ return (signed short)le16_to_cpu(x);
+}
+
+static void applespi_debug_update_dimensions(struct applespi_data *applespi,
+ const struct tp_finger *f)
+{
+ applespi->tp_dim_min_x = min_t(int, applespi->tp_dim_min_x, f->abs_x);
+ applespi->tp_dim_max_x = max_t(int, applespi->tp_dim_max_x, f->abs_x);
+ applespi->tp_dim_min_y = min_t(int, applespi->tp_dim_min_y, f->abs_y);
+ applespi->tp_dim_max_y = max_t(int, applespi->tp_dim_max_y, f->abs_y);
+}
+
+static int applespi_tp_dim_open(struct inode *inode, struct file *file)
+{
+ struct applespi_data *applespi = inode->i_private;
+
+ file->private_data = applespi;
+
+ snprintf(applespi->tp_dim_val, sizeof(applespi->tp_dim_val),
+ "0x%.4x %dx%d+%u+%u\n",
+ applespi->touchpad_input_dev->id.product,
+ applespi->tp_dim_min_x, applespi->tp_dim_min_y,
+ applespi->tp_dim_max_x - applespi->tp_dim_min_x,
+ applespi->tp_dim_max_y - applespi->tp_dim_min_y);
+
+ return nonseekable_open(inode, file);
+}
+
+static ssize_t applespi_tp_dim_read(struct file *file, char __user *buf,
+ size_t len, loff_t *off)
+{
+ struct applespi_data *applespi = file->private_data;
+
+ return simple_read_from_buffer(buf, len, off, applespi->tp_dim_val,
+ strlen(applespi->tp_dim_val));
+}
+
+static const struct file_operations applespi_tp_dim_fops = {
+ .owner = THIS_MODULE,
+ .open = applespi_tp_dim_open,
+ .read = applespi_tp_dim_read,
+ .llseek = no_llseek,
+};
+
+static void report_finger_data(struct input_dev *input, int slot,
+ const struct input_mt_pos *pos,
+ const struct tp_finger *f)
+{
+ input_mt_slot(input, slot);
+ input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+
+ input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+ le16_to_int(f->touch_major) << 1);
+ input_report_abs(input, ABS_MT_TOUCH_MINOR,
+ le16_to_int(f->touch_minor) << 1);
+ input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+ le16_to_int(f->tool_major) << 1);
+ input_report_abs(input, ABS_MT_WIDTH_MINOR,
+ le16_to_int(f->tool_minor) << 1);
+ input_report_abs(input, ABS_MT_ORIENTATION,
+ MAX_FINGER_ORIENTATION - le16_to_int(f->orientation));
+ input_report_abs(input, ABS_MT_POSITION_X, pos->x);
+ input_report_abs(input, ABS_MT_POSITION_Y, pos->y);
+}
+
+static void report_tp_state(struct applespi_data *applespi,
+ struct touchpad_protocol *t)
+{
+ const struct tp_finger *f;
+ struct input_dev *input;
+ const struct applespi_tp_info *tp_info = &applespi->tp_info;
+ int i, n;
+
+ /* touchpad_input_dev is set async in worker */
+ input = smp_load_acquire(&applespi->touchpad_input_dev);
+ if (!input)
+ return; /* touchpad isn't initialized yet */
+
+ n = 0;
+
+ for (i = 0; i < t->number_of_fingers; i++) {
+ f = &t->fingers[i];
+ if (le16_to_int(f->touch_major) == 0)
+ continue;
+ applespi->pos[n].x = le16_to_int(f->abs_x);
+ applespi->pos[n].y = tp_info->y_min + tp_info->y_max -
+ le16_to_int(f->abs_y);
+ n++;
+
+ if (applespi->debug_tp_dim)
+ applespi_debug_update_dimensions(applespi, f);
+ }
+
+ input_mt_assign_slots(input, applespi->slots, applespi->pos, n, 0);
+
+ for (i = 0; i < n; i++)
+ report_finger_data(input, applespi->slots[i],
+ &applespi->pos[i], &t->fingers[i]);
+
+ input_mt_sync_frame(input);
+ input_report_key(input, BTN_LEFT, t->clicked);
+
+ input_sync(input);
+}
+
+static const struct applespi_key_translation *
+applespi_find_translation(const struct applespi_key_translation *table, u16 key)
+{
+ const struct applespi_key_translation *trans;
+
+ for (trans = table; trans->from; trans++)
+ if (trans->from == key)
+ return trans;
+
+ return NULL;
+}
+
+static unsigned int applespi_translate_fn_key(unsigned int key, int fn_pressed)
+{
+ const struct applespi_key_translation *trans;
+ int do_translate;
+
+ trans = applespi_find_translation(applespi_fn_codes, key);
+ if (trans) {
+ if (trans->flags & APPLE_FLAG_FKEY)
+ do_translate = (fnmode == 2 && fn_pressed) ||
+ (fnmode == 1 && !fn_pressed);
+ else
+ do_translate = fn_pressed;
+
+ if (do_translate)
+ key = trans->to;
+ }
+
+ return key;
+}
+
+static unsigned int applespi_translate_iso_layout(unsigned int key)
+{
+ const struct applespi_key_translation *trans;
+
+ trans = applespi_find_translation(apple_iso_keyboard, key);
+ if (trans)
+ key = trans->to;
+
+ return key;
+}
+
+static unsigned int applespi_code_to_key(u8 code, int fn_pressed)
+{
+ unsigned int key = applespi_scancodes[code];
+
+ if (fnmode)
+ key = applespi_translate_fn_key(key, fn_pressed);
+ if (iso_layout)
+ key = applespi_translate_iso_layout(key);
+ return key;
+}
+
+static void
+applespi_remap_fn_key(struct keyboard_protocol *keyboard_protocol)
+{
+ unsigned char tmp;
+ u8 bit = BIT((fnremap - 1) & 0x07);
+
+ if (!fnremap || fnremap > ARRAY_SIZE(applespi_controlcodes) ||
+ !applespi_controlcodes[fnremap - 1])
+ return;
+
+ tmp = keyboard_protocol->fn_pressed;
+ keyboard_protocol->fn_pressed = !!(keyboard_protocol->modifiers & bit);
+ if (tmp)
+ keyboard_protocol->modifiers |= bit;
+ else
+ keyboard_protocol->modifiers &= ~bit;
+}
+
+static void
+applespi_handle_keyboard_event(struct applespi_data *applespi,
+ struct keyboard_protocol *keyboard_protocol)
+{
+ unsigned int key;
+ int i;
+
+ compiletime_assert(ARRAY_SIZE(applespi_controlcodes) ==
+ sizeof_field(struct keyboard_protocol, modifiers) * 8,
+ "applespi_controlcodes has wrong number of entries");
+
+ /* check for rollover overflow, which is signalled by all keys == 1 */
+ if (!memchr_inv(keyboard_protocol->keys_pressed, 1, MAX_ROLLOVER))
+ return;
+
+ /* remap fn key if desired */
+ applespi_remap_fn_key(keyboard_protocol);
+
+ /* check released keys */
+ for (i = 0; i < MAX_ROLLOVER; i++) {
+ if (memchr(keyboard_protocol->keys_pressed,
+ applespi->last_keys_pressed[i], MAX_ROLLOVER))
+ continue; /* key is still pressed */
+
+ key = applespi_code_to_key(applespi->last_keys_pressed[i],
+ applespi->last_keys_fn_pressed[i]);
+ input_report_key(applespi->keyboard_input_dev, key, 0);
+ applespi->last_keys_fn_pressed[i] = 0;
+ }
+
+ /* check pressed keys */
+ for (i = 0; i < MAX_ROLLOVER; i++) {
+ if (keyboard_protocol->keys_pressed[i] <
+ ARRAY_SIZE(applespi_scancodes) &&
+ keyboard_protocol->keys_pressed[i] > 0) {
+ key = applespi_code_to_key(
+ keyboard_protocol->keys_pressed[i],
+ keyboard_protocol->fn_pressed);
+ input_report_key(applespi->keyboard_input_dev, key, 1);
+ applespi->last_keys_fn_pressed[i] =
+ keyboard_protocol->fn_pressed;
+ }
+ }
+
+ /* check control keys */
+ for (i = 0; i < ARRAY_SIZE(applespi_controlcodes); i++) {
+ if (keyboard_protocol->modifiers & BIT(i))
+ input_report_key(applespi->keyboard_input_dev,
+ applespi_controlcodes[i], 1);
+ else
+ input_report_key(applespi->keyboard_input_dev,
+ applespi_controlcodes[i], 0);
+ }
+
+ /* check function key */
+ if (keyboard_protocol->fn_pressed && !applespi->last_fn_pressed)
+ input_report_key(applespi->keyboard_input_dev, KEY_FN, 1);
+ else if (!keyboard_protocol->fn_pressed && applespi->last_fn_pressed)
+ input_report_key(applespi->keyboard_input_dev, KEY_FN, 0);
+ applespi->last_fn_pressed = keyboard_protocol->fn_pressed;
+
+ /* done */
+ input_sync(applespi->keyboard_input_dev);
+ memcpy(&applespi->last_keys_pressed, keyboard_protocol->keys_pressed,
+ sizeof(applespi->last_keys_pressed));
+}
+
+static const struct applespi_tp_info *applespi_find_touchpad_info(__u8 model)
+{
+ const struct applespi_tp_model_info *info;
+
+ for (info = applespi_tp_models; info->model; info++) {
+ if (info->model == model)
+ return &info->tp_info;
+ }
+
+ return NULL;
+}
+
+static int
+applespi_register_touchpad_device(struct applespi_data *applespi,
+ struct touchpad_info_protocol *rcvd_tp_info)
+{
+ const struct applespi_tp_info *tp_info;
+ struct input_dev *touchpad_input_dev;
+ int sts;
+
+ /* set up touchpad dimensions */
+ tp_info = applespi_find_touchpad_info(rcvd_tp_info->model_no);
+ if (!tp_info) {
+ dev_warn(&applespi->spi->dev,
+ "Unknown touchpad model %x - falling back to MB8 touchpad\n",
+ rcvd_tp_info->model_no);
+ tp_info = &applespi_tp_models[0].tp_info;
+ }
+
+ applespi->tp_info = *tp_info;
+
+ if (touchpad_dimensions[0]) {
+ int x, y, w, h;
+
+ sts = sscanf(touchpad_dimensions, "%dx%d+%u+%u", &x, &y, &w, &h);
+ if (sts == 4) {
+ dev_info(&applespi->spi->dev,
+ "Overriding touchpad dimensions from module param\n");
+ applespi->tp_info.x_min = x;
+ applespi->tp_info.y_min = y;
+ applespi->tp_info.x_max = x + w;
+ applespi->tp_info.y_max = y + h;
+ } else {
+ dev_warn(&applespi->spi->dev,
+ "Invalid touchpad dimensions '%s': must be in the form XxY+W+H\n",
+ touchpad_dimensions);
+ touchpad_dimensions[0] = '\0';
+ }
+ }
+ if (!touchpad_dimensions[0]) {
+ snprintf(touchpad_dimensions, sizeof(touchpad_dimensions),
+ "%dx%d+%u+%u",
+ applespi->tp_info.x_min,
+ applespi->tp_info.y_min,
+ applespi->tp_info.x_max - applespi->tp_info.x_min,
+ applespi->tp_info.y_max - applespi->tp_info.y_min);
+ }
+
+ /* create touchpad input device */
+ touchpad_input_dev = devm_input_allocate_device(&applespi->spi->dev);
+ if (!touchpad_input_dev) {
+ dev_err(&applespi->spi->dev,
+ "Failed to allocate touchpad input device\n");
+ return -ENOMEM;
+ }
+
+ touchpad_input_dev->name = "Apple SPI Touchpad";
+ touchpad_input_dev->phys = "applespi/input1";
+ touchpad_input_dev->dev.parent = &applespi->spi->dev;
+ touchpad_input_dev->id.bustype = BUS_SPI;
+ touchpad_input_dev->id.vendor = SYNAPTICS_VENDOR_ID;
+ touchpad_input_dev->id.product =
+ rcvd_tp_info->model_no << 8 | rcvd_tp_info->model_flags;
+
+ /* basic properties */
+ input_set_capability(touchpad_input_dev, EV_REL, REL_X);
+ input_set_capability(touchpad_input_dev, EV_REL, REL_Y);
+
+ __set_bit(INPUT_PROP_POINTER, touchpad_input_dev->propbit);
+ __set_bit(INPUT_PROP_BUTTONPAD, touchpad_input_dev->propbit);
+
+ /* finger touch area */
+ input_set_abs_params(touchpad_input_dev, ABS_MT_TOUCH_MAJOR,
+ 0, 5000, 0, 0);
+ input_set_abs_params(touchpad_input_dev, ABS_MT_TOUCH_MINOR,
+ 0, 5000, 0, 0);
+
+ /* finger approach area */
+ input_set_abs_params(touchpad_input_dev, ABS_MT_WIDTH_MAJOR,
+ 0, 5000, 0, 0);
+ input_set_abs_params(touchpad_input_dev, ABS_MT_WIDTH_MINOR,
+ 0, 5000, 0, 0);
+
+ /* finger orientation */
+ input_set_abs_params(touchpad_input_dev, ABS_MT_ORIENTATION,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION,
+ 0, 0);
+
+ /* finger position */
+ input_set_abs_params(touchpad_input_dev, ABS_MT_POSITION_X,
+ applespi->tp_info.x_min, applespi->tp_info.x_max,
+ 0, 0);
+ input_set_abs_params(touchpad_input_dev, ABS_MT_POSITION_Y,
+ applespi->tp_info.y_min, applespi->tp_info.y_max,
+ 0, 0);
+
+ /* touchpad button */
+ input_set_capability(touchpad_input_dev, EV_KEY, BTN_LEFT);
+
+ /* multitouch */
+ input_mt_init_slots(touchpad_input_dev, MAX_FINGERS,
+ INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED |
+ INPUT_MT_TRACK);
+
+ /* register input device */
+ sts = input_register_device(touchpad_input_dev);
+ if (sts) {
+ dev_err(&applespi->spi->dev,
+ "Unable to register touchpad input device (%d)\n", sts);
+ return sts;
+ }
+
+ /* touchpad_input_dev is read async in spi callback */
+ smp_store_release(&applespi->touchpad_input_dev, touchpad_input_dev);
+
+ return 0;
+}
+
+static void applespi_worker(struct work_struct *work)
+{
+ struct applespi_data *applespi =
+ container_of(work, struct applespi_data, work);
+
+ applespi_register_touchpad_device(applespi, &applespi->rcvd_tp_info);
+}
+
+static void applespi_handle_cmd_response(struct applespi_data *applespi,
+ struct spi_packet *packet,
+ struct message *message)
+{
+ if (packet->device == PACKET_DEV_INFO &&
+ le16_to_cpu(message->type) == 0x1020) {
+ /*
+ * We're not allowed to sleep here, but registering an input
+ * device can sleep.
+ */
+ applespi->rcvd_tp_info = message->tp_info;
+ schedule_work(&applespi->work);
+ return;
+ }
+
+ if (le16_to_cpu(message->length) != 0x0000) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received unexpected write response: length=%x\n",
+ le16_to_cpu(message->length));
+ return;
+ }
+
+ if (packet->device == PACKET_DEV_TPAD &&
+ le16_to_cpu(message->type) == 0x0252 &&
+ le16_to_cpu(message->rsp_buf_len) == 0x0002)
+ dev_info(&applespi->spi->dev, "modeswitch done.\n");
+}
+
+static bool applespi_verify_crc(struct applespi_data *applespi, u8 *buffer,
+ size_t buflen)
+{
+ u16 crc;
+
+ crc = crc16(0, buffer, buflen);
+ if (crc) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received corrupted packet (crc mismatch)\n");
+ trace_applespi_bad_crc(ET_RD_CRC, READ, buffer, buflen);
+
+ return false;
+ }
+
+ return true;
+}
+
+static void applespi_debug_print_read_packet(struct applespi_data *applespi,
+ struct spi_packet *packet)
+{
+ unsigned int evt_type;
+
+ if (packet->flags == PACKET_TYPE_READ &&
+ packet->device == PACKET_DEV_KEYB)
+ evt_type = ET_RD_KEYB;
+ else if (packet->flags == PACKET_TYPE_READ &&
+ packet->device == PACKET_DEV_TPAD)
+ evt_type = ET_RD_TPAD;
+ else if (packet->flags == PACKET_TYPE_WRITE)
+ evt_type = applespi->cmd_evt_type;
+ else
+ evt_type = ET_RD_UNKN;
+
+ applespi_get_trace_fun(evt_type)(evt_type, PT_READ, applespi->rx_buffer,
+ APPLESPI_PACKET_SIZE);
+}
+
+static void applespi_got_data(struct applespi_data *applespi)
+{
+ struct spi_packet *packet;
+ struct message *message;
+ unsigned int msg_len;
+ unsigned int off;
+ unsigned int rem;
+ unsigned int len;
+
+ /* process packet header */
+ if (!applespi_verify_crc(applespi, applespi->rx_buffer,
+ APPLESPI_PACKET_SIZE)) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ if (applespi->drain) {
+ applespi->read_active = false;
+ applespi->write_active = false;
+
+ wake_up_all(&applespi->drain_complete);
+ }
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+
+ return;
+ }
+
+ packet = (struct spi_packet *)applespi->rx_buffer;
+
+ applespi_debug_print_read_packet(applespi, packet);
+
+ off = le16_to_cpu(packet->offset);
+ rem = le16_to_cpu(packet->remaining);
+ len = le16_to_cpu(packet->length);
+
+ if (len > sizeof(packet->data)) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received corrupted packet (invalid packet length %u)\n",
+ len);
+ goto msg_complete;
+ }
+
+ /* handle multi-packet messages */
+ if (rem > 0 || off > 0) {
+ if (off != applespi->saved_msg_len) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received unexpected offset (got %u, expected %u)\n",
+ off, applespi->saved_msg_len);
+ goto msg_complete;
+ }
+
+ if (off + rem > MAX_PKTS_PER_MSG * APPLESPI_PACKET_SIZE) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received message too large (size %u)\n",
+ off + rem);
+ goto msg_complete;
+ }
+
+ if (off + len > MAX_PKTS_PER_MSG * APPLESPI_PACKET_SIZE) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received message too large (size %u)\n",
+ off + len);
+ goto msg_complete;
+ }
+
+ memcpy(applespi->msg_buf + off, &packet->data, len);
+ applespi->saved_msg_len += len;
+
+ if (rem > 0)
+ return;
+
+ message = (struct message *)applespi->msg_buf;
+ msg_len = applespi->saved_msg_len;
+ } else {
+ message = (struct message *)&packet->data;
+ msg_len = len;
+ }
+
+ /* got complete message - verify */
+ if (!applespi_verify_crc(applespi, (u8 *)message, msg_len))
+ goto msg_complete;
+
+ if (le16_to_cpu(message->length) != msg_len - MSG_HEADER_SIZE - 2) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received corrupted packet (invalid message length %u - expected %u)\n",
+ le16_to_cpu(message->length),
+ msg_len - MSG_HEADER_SIZE - 2);
+ goto msg_complete;
+ }
+
+ /* handle message */
+ if (packet->flags == PACKET_TYPE_READ &&
+ packet->device == PACKET_DEV_KEYB) {
+ applespi_handle_keyboard_event(applespi, &message->keyboard);
+
+ } else if (packet->flags == PACKET_TYPE_READ &&
+ packet->device == PACKET_DEV_TPAD) {
+ struct touchpad_protocol *tp;
+ size_t tp_len;
+
+ tp = &message->touchpad;
+ tp_len = sizeof(*tp) +
+ tp->number_of_fingers * sizeof(tp->fingers[0]);
+
+ if (le16_to_cpu(message->length) + 2 != tp_len) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Received corrupted packet (invalid message length %u - num-fingers %u, tp-len %zu)\n",
+ le16_to_cpu(message->length),
+ tp->number_of_fingers, tp_len);
+ goto msg_complete;
+ }
+
+ if (tp->number_of_fingers > MAX_FINGERS) {
+ dev_warn_ratelimited(&applespi->spi->dev,
+ "Number of reported fingers (%u) exceeds max (%u))\n",
+ tp->number_of_fingers,
+ MAX_FINGERS);
+ tp->number_of_fingers = MAX_FINGERS;
+ }
+
+ report_tp_state(applespi, tp);
+
+ } else if (packet->flags == PACKET_TYPE_WRITE) {
+ applespi_handle_cmd_response(applespi, packet, message);
+ }
+
+msg_complete:
+ applespi->saved_msg_len = 0;
+
+ applespi_msg_complete(applespi, packet->flags == PACKET_TYPE_WRITE,
+ true);
+}
+
+static void applespi_async_read_complete(void *context)
+{
+ struct applespi_data *applespi = context;
+
+ if (applespi->rd_m.status < 0) {
+ dev_warn(&applespi->spi->dev, "Error reading from device: %d\n",
+ applespi->rd_m.status);
+ /*
+ * We don't actually know if this was a pure read, or a response
+ * to a write. But this is a rare error condition that should
+ * never occur, so clearing both flags to avoid deadlock.
+ */
+ applespi_msg_complete(applespi, true, true);
+ } else {
+ applespi_got_data(applespi);
+ }
+
+ acpi_finish_gpe(NULL, applespi->gpe);
+}
+
+static u32 applespi_notify(acpi_handle gpe_device, u32 gpe, void *context)
+{
+ struct applespi_data *applespi = context;
+ int sts;
+ unsigned long flags;
+
+ trace_applespi_irq_received(ET_RD_IRQ, PT_READ);
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ if (!applespi->suspended) {
+ sts = applespi_async(applespi, &applespi->rd_m,
+ applespi_async_read_complete);
+ if (sts)
+ dev_warn(&applespi->spi->dev,
+ "Error queueing async read to device: %d\n",
+ sts);
+ else
+ applespi->read_active = true;
+ }
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+
+ return ACPI_INTERRUPT_HANDLED;
+}
+
+static int applespi_get_saved_bl_level(struct applespi_data *applespi)
+{
+ struct efivar_entry *efivar_entry;
+ u16 efi_data = 0;
+ unsigned long efi_data_len;
+ int sts;
+
+ efivar_entry = kmalloc(sizeof(*efivar_entry), GFP_KERNEL);
+ if (!efivar_entry)
+ return -ENOMEM;
+
+ memcpy(efivar_entry->var.VariableName, EFI_BL_LEVEL_NAME,
+ sizeof(EFI_BL_LEVEL_NAME));
+ efivar_entry->var.VendorGuid = EFI_BL_LEVEL_GUID;
+ efi_data_len = sizeof(efi_data);
+
+ sts = efivar_entry_get(efivar_entry, NULL, &efi_data_len, &efi_data);
+ if (sts && sts != -ENOENT)
+ dev_warn(&applespi->spi->dev,
+ "Error getting backlight level from EFI vars: %d\n",
+ sts);
+
+ kfree(efivar_entry);
+
+ return sts ? sts : efi_data;
+}
+
+static void applespi_save_bl_level(struct applespi_data *applespi,
+ unsigned int level)
+{
+ efi_guid_t efi_guid;
+ u32 efi_attr;
+ unsigned long efi_data_len;
+ u16 efi_data;
+ int sts;
+
+ /* Save keyboard backlight level */
+ efi_guid = EFI_BL_LEVEL_GUID;
+ efi_data = (u16)level;
+ efi_data_len = sizeof(efi_data);
+ efi_attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS;
+
+ sts = efivar_entry_set_safe(EFI_BL_LEVEL_NAME, efi_guid, efi_attr, true,
+ efi_data_len, &efi_data);
+ if (sts)
+ dev_warn(&applespi->spi->dev,
+ "Error saving backlight level to EFI vars: %d\n", sts);
+}
+
+static int applespi_probe(struct spi_device *spi)
+{
+ struct applespi_data *applespi;
+ acpi_handle spi_handle = ACPI_HANDLE(&spi->dev);
+ acpi_status acpi_sts;
+ int sts, i;
+ unsigned long long gpe, usb_status;
+
+ /* check if the USB interface is present and enabled already */
+ acpi_sts = acpi_evaluate_integer(spi_handle, "UIST", NULL, &usb_status);
+ if (ACPI_SUCCESS(acpi_sts) && usb_status) {
+ /* let the USB driver take over instead */
+ dev_info(&spi->dev, "USB interface already enabled\n");
+ return -ENODEV;
+ }
+
+ /* allocate driver data */
+ applespi = devm_kzalloc(&spi->dev, sizeof(*applespi), GFP_KERNEL);
+ if (!applespi)
+ return -ENOMEM;
+
+ applespi->spi = spi;
+
+ INIT_WORK(&applespi->work, applespi_worker);
+
+ /* store the driver data */
+ spi_set_drvdata(spi, applespi);
+
+ /* create our buffers */
+ applespi->tx_buffer = devm_kmalloc(&spi->dev, APPLESPI_PACKET_SIZE,
+ GFP_KERNEL);
+ applespi->tx_status = devm_kmalloc(&spi->dev, APPLESPI_STATUS_SIZE,
+ GFP_KERNEL);
+ applespi->rx_buffer = devm_kmalloc(&spi->dev, APPLESPI_PACKET_SIZE,
+ GFP_KERNEL);
+ applespi->msg_buf = devm_kmalloc_array(&spi->dev, MAX_PKTS_PER_MSG,
+ APPLESPI_PACKET_SIZE,
+ GFP_KERNEL);
+
+ if (!applespi->tx_buffer || !applespi->tx_status ||
+ !applespi->rx_buffer || !applespi->msg_buf)
+ return -ENOMEM;
+
+ /* set up our spi messages */
+ applespi_setup_read_txfrs(applespi);
+ applespi_setup_write_txfrs(applespi);
+
+ /* cache ACPI method handles */
+ acpi_sts = acpi_get_handle(spi_handle, "SIEN", &applespi->sien);
+ if (ACPI_FAILURE(acpi_sts)) {
+ dev_err(&applespi->spi->dev,
+ "Failed to get SIEN ACPI method handle: %s\n",
+ acpi_format_exception(acpi_sts));
+ return -ENODEV;
+ }
+
+ acpi_sts = acpi_get_handle(spi_handle, "SIST", &applespi->sist);
+ if (ACPI_FAILURE(acpi_sts)) {
+ dev_err(&applespi->spi->dev,
+ "Failed to get SIST ACPI method handle: %s\n",
+ acpi_format_exception(acpi_sts));
+ return -ENODEV;
+ }
+
+ /* switch on the SPI interface */
+ sts = applespi_setup_spi(applespi);
+ if (sts)
+ return sts;
+
+ sts = applespi_enable_spi(applespi);
+ if (sts)
+ return sts;
+
+ /* setup the keyboard input dev */
+ applespi->keyboard_input_dev = devm_input_allocate_device(&spi->dev);
+
+ if (!applespi->keyboard_input_dev)
+ return -ENOMEM;
+
+ applespi->keyboard_input_dev->name = "Apple SPI Keyboard";
+ applespi->keyboard_input_dev->phys = "applespi/input0";
+ applespi->keyboard_input_dev->dev.parent = &spi->dev;
+ applespi->keyboard_input_dev->id.bustype = BUS_SPI;
+
+ applespi->keyboard_input_dev->evbit[0] =
+ BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) | BIT_MASK(EV_REP);
+ applespi->keyboard_input_dev->ledbit[0] = BIT_MASK(LED_CAPSL);
+
+ input_set_drvdata(applespi->keyboard_input_dev, applespi);
+ applespi->keyboard_input_dev->event = applespi_event;
+
+ for (i = 0; i < ARRAY_SIZE(applespi_scancodes); i++)
+ if (applespi_scancodes[i])
+ input_set_capability(applespi->keyboard_input_dev,
+ EV_KEY, applespi_scancodes[i]);
+
+ for (i = 0; i < ARRAY_SIZE(applespi_controlcodes); i++)
+ if (applespi_controlcodes[i])
+ input_set_capability(applespi->keyboard_input_dev,
+ EV_KEY, applespi_controlcodes[i]);
+
+ for (i = 0; i < ARRAY_SIZE(applespi_fn_codes); i++)
+ if (applespi_fn_codes[i].to)
+ input_set_capability(applespi->keyboard_input_dev,
+ EV_KEY, applespi_fn_codes[i].to);
+
+ input_set_capability(applespi->keyboard_input_dev, EV_KEY, KEY_FN);
+
+ sts = input_register_device(applespi->keyboard_input_dev);
+ if (sts) {
+ dev_err(&applespi->spi->dev,
+ "Unable to register keyboard input device (%d)\n", sts);
+ return -ENODEV;
+ }
+
+ /*
+ * The applespi device doesn't send interrupts normally (as is described
+ * in its DSDT), but rather seems to use ACPI GPEs.
+ */
+ acpi_sts = acpi_evaluate_integer(spi_handle, "_GPE", NULL, &gpe);
+ if (ACPI_FAILURE(acpi_sts)) {
+ dev_err(&applespi->spi->dev,
+ "Failed to obtain GPE for SPI slave device: %s\n",
+ acpi_format_exception(acpi_sts));
+ return -ENODEV;
+ }
+ applespi->gpe = (int)gpe;
+
+ acpi_sts = acpi_install_gpe_handler(NULL, applespi->gpe,
+ ACPI_GPE_LEVEL_TRIGGERED,
+ applespi_notify, applespi);
+ if (ACPI_FAILURE(acpi_sts)) {
+ dev_err(&applespi->spi->dev,
+ "Failed to install GPE handler for GPE %d: %s\n",
+ applespi->gpe, acpi_format_exception(acpi_sts));
+ return -ENODEV;
+ }
+
+ applespi->suspended = false;
+
+ acpi_sts = acpi_enable_gpe(NULL, applespi->gpe);
+ if (ACPI_FAILURE(acpi_sts)) {
+ dev_err(&applespi->spi->dev,
+ "Failed to enable GPE handler for GPE %d: %s\n",
+ applespi->gpe, acpi_format_exception(acpi_sts));
+ acpi_remove_gpe_handler(NULL, applespi->gpe, applespi_notify);
+ return -ENODEV;
+ }
+
+ /* trigger touchpad setup */
+ applespi_init(applespi, false);
+
+ /*
+ * By default this device is not enabled for wakeup; but USB keyboards
+ * generally are, so the expectation is that by default the keyboard
+ * will wake the system.
+ */
+ device_wakeup_enable(&spi->dev);
+
+ /* set up keyboard-backlight */
+ sts = applespi_get_saved_bl_level(applespi);
+ if (sts >= 0)
+ applespi_set_bl_level(&applespi->backlight_info, sts);
+
+ applespi->backlight_info.name = "spi::kbd_backlight";
+ applespi->backlight_info.default_trigger = "kbd-backlight";
+ applespi->backlight_info.brightness_set = applespi_set_bl_level;
+
+ sts = devm_led_classdev_register(&spi->dev, &applespi->backlight_info);
+ if (sts)
+ dev_warn(&applespi->spi->dev,
+ "Unable to register keyboard backlight class dev (%d)\n",
+ sts);
+
+ /* set up debugfs entries for touchpad dimensions logging */
+ applespi->debugfs_root = debugfs_create_dir("applespi", NULL);
+ if (IS_ERR(applespi->debugfs_root)) {
+ if (PTR_ERR(applespi->debugfs_root) != -ENODEV)
+ dev_warn(&applespi->spi->dev,
+ "Error creating debugfs root entry (%ld)\n",
+ PTR_ERR(applespi->debugfs_root));
+ } else {
+ struct dentry *ret;
+
+ ret = debugfs_create_bool("enable_tp_dim", 0600,
+ applespi->debugfs_root,
+ &applespi->debug_tp_dim);
+ if (IS_ERR(ret))
+ dev_dbg(&applespi->spi->dev,
+ "Error creating debugfs entry enable_tp_dim (%ld)\n",
+ PTR_ERR(ret));
+
+ ret = debugfs_create_file("tp_dim", 0400,
+ applespi->debugfs_root, applespi,
+ &applespi_tp_dim_fops);
+ if (IS_ERR(ret))
+ dev_dbg(&applespi->spi->dev,
+ "Error creating debugfs entry tp_dim (%ld)\n",
+ PTR_ERR(ret));
+ }
+
+ return 0;
+}
+
+static void applespi_drain_writes(struct applespi_data *applespi)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ applespi->drain = true;
+ wait_event_lock_irq(applespi->drain_complete, !applespi->write_active,
+ applespi->cmd_msg_lock);
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+}
+
+static void applespi_drain_reads(struct applespi_data *applespi)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ wait_event_lock_irq(applespi->drain_complete, !applespi->read_active,
+ applespi->cmd_msg_lock);
+
+ applespi->suspended = true;
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+}
+
+static int applespi_remove(struct spi_device *spi)
+{
+ struct applespi_data *applespi = spi_get_drvdata(spi);
+
+ applespi_drain_writes(applespi);
+
+ acpi_disable_gpe(NULL, applespi->gpe);
+ acpi_remove_gpe_handler(NULL, applespi->gpe, applespi_notify);
+ device_wakeup_disable(&spi->dev);
+
+ applespi_drain_reads(applespi);
+
+ debugfs_remove_recursive(applespi->debugfs_root);
+
+ return 0;
+}
+
+static void applespi_shutdown(struct spi_device *spi)
+{
+ struct applespi_data *applespi = spi_get_drvdata(spi);
+
+ applespi_save_bl_level(applespi, applespi->have_bl_level);
+}
+
+static int applespi_poweroff_late(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct applespi_data *applespi = spi_get_drvdata(spi);
+
+ applespi_save_bl_level(applespi, applespi->have_bl_level);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int applespi_suspend(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct applespi_data *applespi = spi_get_drvdata(spi);
+ acpi_status acpi_sts;
+ int sts;
+
+ /* turn off caps-lock - it'll stay on otherwise */
+ sts = applespi_set_capsl_led(applespi, false);
+ if (sts)
+ dev_warn(&applespi->spi->dev,
+ "Failed to turn off caps-lock led (%d)\n", sts);
+
+ applespi_drain_writes(applespi);
+
+ /* disable the interrupt */
+ acpi_sts = acpi_disable_gpe(NULL, applespi->gpe);
+ if (ACPI_FAILURE(acpi_sts))
+ dev_err(&applespi->spi->dev,
+ "Failed to disable GPE handler for GPE %d: %s\n",
+ applespi->gpe, acpi_format_exception(acpi_sts));
+
+ applespi_drain_reads(applespi);
+
+ return 0;
+}
+
+static int applespi_resume(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct applespi_data *applespi = spi_get_drvdata(spi);
+ acpi_status acpi_sts;
+ unsigned long flags;
+
+ /* ensure our flags and state reflect a newly resumed device */
+ spin_lock_irqsave(&applespi->cmd_msg_lock, flags);
+
+ applespi->drain = false;
+ applespi->have_cl_led_on = false;
+ applespi->have_bl_level = 0;
+ applespi->cmd_msg_queued = false;
+ applespi->read_active = false;
+ applespi->write_active = false;
+
+ applespi->suspended = false;
+
+ spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags);
+
+ /* switch on the SPI interface */
+ applespi_enable_spi(applespi);
+
+ /* re-enable the interrupt */
+ acpi_sts = acpi_enable_gpe(NULL, applespi->gpe);
+ if (ACPI_FAILURE(acpi_sts))
+ dev_err(&applespi->spi->dev,
+ "Failed to re-enable GPE handler for GPE %d: %s\n",
+ applespi->gpe, acpi_format_exception(acpi_sts));
+
+ /* switch the touchpad into multitouch mode */
+ applespi_init(applespi, true);
+
+ return 0;
+}
+#endif
+
+static const struct acpi_device_id applespi_acpi_match[] = {
+ { "APP000D", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, applespi_acpi_match);
+
+const struct dev_pm_ops applespi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(applespi_suspend, applespi_resume)
+ .poweroff_late = applespi_poweroff_late,
+};
+
+static struct spi_driver applespi_driver = {
+ .driver = {
+ .name = "applespi",
+ .acpi_match_table = applespi_acpi_match,
+ .pm = &applespi_pm_ops,
+ },
+ .probe = applespi_probe,
+ .remove = applespi_remove,
+ .shutdown = applespi_shutdown,
+};
+
+module_spi_driver(applespi_driver)
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MacBook(Pro) SPI Keyboard/Touchpad driver");
+MODULE_AUTHOR("Federico Lorenzi");
+MODULE_AUTHOR("Ronald Tschalär");
diff --git a/drivers/input/keyboard/applespi.h b/drivers/input/keyboard/applespi.h
new file mode 100644
index 000000000000..7f5ab10c597a
--- /dev/null
+++ b/drivers/input/keyboard/applespi.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * MacBook (Pro) SPI keyboard and touchpad driver
+ *
+ * Copyright (c) 2015-2019 Federico Lorenzi
+ * Copyright (c) 2017-2019 Ronald Tschalär
+ */
+
+#ifndef _APPLESPI_H_
+#define _APPLESPI_H_
+
+enum applespi_evt_type {
+ ET_CMD_TP_INI = BIT(0),
+ ET_CMD_BL = BIT(1),
+ ET_CMD_CL = BIT(2),
+ ET_RD_KEYB = BIT(8),
+ ET_RD_TPAD = BIT(9),
+ ET_RD_UNKN = BIT(10),
+ ET_RD_IRQ = BIT(11),
+ ET_RD_CRC = BIT(12),
+};
+
+enum applespi_pkt_type {
+ PT_READ,
+ PT_WRITE,
+ PT_STATUS,
+};
+
+#endif /* _APPLESPI_H_ */
diff --git a/drivers/input/keyboard/applespi_trace.h b/drivers/input/keyboard/applespi_trace.h
new file mode 100644
index 000000000000..5e965e1974c7
--- /dev/null
+++ b/drivers/input/keyboard/applespi_trace.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * MacBook (Pro) SPI keyboard and touchpad driver
+ *
+ * Copyright (c) 2015-2019 Federico Lorenzi
+ * Copyright (c) 2017-2019 Ronald Tschalär
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM applespi
+
+#if !defined(_APPLESPI_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
+#define _APPLESPI_TRACE_H_
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+#include "applespi.h"
+
+DECLARE_EVENT_CLASS(dump_message_template,
+ TP_PROTO(enum applespi_evt_type evt_type,
+ enum applespi_pkt_type pkt_type,
+ u8 *buf,
+ size_t len),
+
+ TP_ARGS(evt_type, pkt_type, buf, len),
+
+ TP_STRUCT__entry(
+ __field(enum applespi_evt_type, evt_type)
+ __field(enum applespi_pkt_type, pkt_type)
+ __field(size_t, len)
+ __dynamic_array(u8, buf, len)
+ ),
+
+ TP_fast_assign(
+ __entry->evt_type = evt_type;
+ __entry->pkt_type = pkt_type;
+ __entry->len = len;
+ memcpy(__get_dynamic_array(buf), buf, len);
+ ),
+
+ TP_printk("%-6s: %s",
+ __print_symbolic(__entry->pkt_type,
+ { PT_READ, "read" },
+ { PT_WRITE, "write" },
+ { PT_STATUS, "status" }
+ ),
+ __print_hex(__get_dynamic_array(buf), __entry->len))
+);
+
+#define DEFINE_DUMP_MESSAGE_EVENT(name) \
+DEFINE_EVENT(dump_message_template, name, \
+ TP_PROTO(enum applespi_evt_type evt_type, \
+ enum applespi_pkt_type pkt_type, \
+ u8 *buf, \
+ size_t len), \
+ TP_ARGS(evt_type, pkt_type, buf, len) \
+)
+
+DEFINE_DUMP_MESSAGE_EVENT(applespi_tp_ini_cmd);
+DEFINE_DUMP_MESSAGE_EVENT(applespi_backlight_cmd);
+DEFINE_DUMP_MESSAGE_EVENT(applespi_caps_lock_cmd);
+DEFINE_DUMP_MESSAGE_EVENT(applespi_keyboard_data);
+DEFINE_DUMP_MESSAGE_EVENT(applespi_touchpad_data);
+DEFINE_DUMP_MESSAGE_EVENT(applespi_unknown_data);
+DEFINE_DUMP_MESSAGE_EVENT(applespi_bad_crc);
+
+TRACE_EVENT(applespi_irq_received,
+ TP_PROTO(enum applespi_evt_type evt_type,
+ enum applespi_pkt_type pkt_type),
+
+ TP_ARGS(evt_type, pkt_type),
+
+ TP_STRUCT__entry(
+ __field(enum applespi_evt_type, evt_type)
+ __field(enum applespi_pkt_type, pkt_type)
+ ),
+
+ TP_fast_assign(
+ __entry->evt_type = evt_type;
+ __entry->pkt_type = pkt_type;
+ ),
+
+ "\n"
+);
+
+#endif /* _APPLESPI_TRACE_H_ */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH ../../drivers/input/keyboard
+#define TRACE_INCLUDE_FILE applespi_trace
+#include <trace/define_trace.h>
+
--
2.20.1
^ permalink raw reply related
* Re: [PATCH v2] Input: uinput: Avoid Object-Already-Free with a global lock
From: Mukesh Ojha @ 2019-04-19 8:43 UTC (permalink / raw)
To: dmitry.torokhov@gmail.com
Cc: linux-input, linux-kernel, Gaurav Kohli, Peter Hutterer,
Martin Kepplinger, Paul E. McKenney
In-Reply-To: <20190419071152.x5ghvbybjhv76uxt@penguin>
On 4/19/2019 12:41 PM, dmitry.torokhov@gmail.com wrote:
> Hi Mukesh,
>
> On Fri, Apr 19, 2019 at 12:17:44PM +0530, Mukesh Ojha wrote:
>> For some reason my last mail did not get delivered, sending it again.
>>
>>
>> On 4/18/2019 11:55 AM, Mukesh Ojha wrote:
>>>
>>> On 4/18/2019 7:13 AM, dmitry.torokhov@gmail.com wrote:
>>>> Hi Mukesh,
>>>>
>>>> On Mon, Apr 15, 2019 at 03:35:51PM +0530, Mukesh Ojha wrote:
>>>>> Hi Dmitry,
>>>>>
>>>>> Can you please have a look at this patch ? as this seems to reproducing
>>>>> quite frequently
>>>>>
>>>>> Thanks,
>>>>> Mukesh
>>>>>
>>>>> On 4/10/2019 1:29 PM, Mukesh Ojha wrote:
>>>>>> uinput_destroy_device() gets called from two places. In one place,
>>>>>> uinput_ioctl_handler() where it is protected under a lock
>>>>>> udev->mutex but there is no protection on udev device from freeing
>>>>>> inside uinput_release().
>>>> uinput_release() should be called when last file handle to the uinput
>>>> instance is being dropped, so there should be no other users and thus we
>>>> can't be racing with anyone.
>>> Lets say an example where i am creating input device quite frequently
>>>
>>> [ 97.836603] input: syz0 as /devices/virtual/input/input262
>>> [ 97.845589] input: syz0 as /devices/virtual/input/input261
>>> [ 97.849415] input: syz0 as /devices/virtual/input/input263
>>> [ 97.856479] input: syz0 as /devices/virtual/input/input264
>>> [ 97.936128] input: syz0 as /devices/virtual/input/input265
>>>
>>> e.g input265
>>>
>>> while input265 gets created [1] and handlers are getting registered on
>>> that device*fput* gets called on
>>> that device as user space got to know that input265 is created and its
>>> reference is still 1(rare but possible).
> Are you saying that there are 2 threads sharing the same file
> descriptor, one issuing the registration ioctl while the other closing
> the same fd?
Dmitry,
I don't have a the exact look inside the app here, but this looks like
the same as it is able to do
fput on the uinput device.
FYI
Syskaller app is running in userspace (which is for syscall fuzzing) on
kernel which is enabled
with various config fault injection, FAULT_INJECTION,FAIL_SLAB,
FAIL_PAGEALLOC, KASAN etc.
Thanks,
Mukesh
>
> Thanks.
>
^ permalink raw reply
* Re: [git pull] Input updates for v5.1-rc5
From: pr-tracker-bot @ 2019-04-19 19:10 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Linus Torvalds, linux-kernel, linux-input
In-Reply-To: <20190419073157.epllrynbv5ww4a7r@penguin>
The pull request you sent on Fri, 19 Apr 2019 07:31:57 +0000:
> git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/240206fcab661afe9bed72e8704cef1d6e83e338
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.wiki.kernel.org/userdoc/prtracker
^ permalink raw reply
* Re: [PATCH v3 09/26] compat_ioctl: move drivers to compat_ptr_ioctl
From: Michael S. Tsirkin @ 2019-04-19 23:16 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Alexander Viro, linux-fsdevel, y2038, linux-kernel,
Greg Kroah-Hartman, Jarkko Sakkinen, Jason Gunthorpe,
Sudip Mukherjee, Peter Huewe, Jason Gunthorpe, Stefan Richter,
Jiri Kosina, Benjamin Tissoires, Alexander Shishkin,
Maxime Coquelin, Alexandre Torgue, Tomas Winkler,
Artem Bityutskiy, Richard Weinberger
In-Reply-To: <20190416202013.4034148-10-arnd@arndb.de>
On Tue, Apr 16, 2019 at 10:19:47PM +0200, Arnd Bergmann wrote:
> Each of these drivers has a copy of the same trivial helper function to
> convert the pointer argument and then call the native ioctl handler.
>
> We now have a generic implementation of that, so use it.
>
> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Reviewed-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
> Reviewed-by: Jason Gunthorpe <jgg@mellanox.com>
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Michael S. Tsirkin <mst@redhat.com>
> ---
> drivers/char/ppdev.c | 12 +---------
> drivers/char/tpm/tpm_vtpm_proxy.c | 12 +---------
> drivers/firewire/core-cdev.c | 12 +---------
> drivers/hid/usbhid/hiddev.c | 11 +--------
> drivers/hwtracing/stm/core.c | 12 +---------
> drivers/misc/mei/main.c | 22 +----------------
> drivers/mtd/ubi/cdev.c | 36 +++-------------------------
> drivers/net/tap.c | 12 +---------
> drivers/staging/pi433/pi433_if.c | 12 +---------
> drivers/usb/core/devio.c | 16 +------------
> drivers/vfio/vfio.c | 39 +++----------------------------
> drivers/vhost/net.c | 12 +---------
> drivers/vhost/scsi.c | 12 +---------
> drivers/vhost/test.c | 12 +---------
> drivers/vhost/vsock.c | 12 +---------
> fs/fat/file.c | 13 +----------
> 16 files changed, 20 insertions(+), 237 deletions(-)
>
> diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c
> index 1ae77b41050a..e96c8d9623e0 100644
> --- a/drivers/char/ppdev.c
> +++ b/drivers/char/ppdev.c
> @@ -674,14 +674,6 @@ static long pp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> return ret;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long pp_compat_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> -{
> - return pp_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static int pp_open(struct inode *inode, struct file *file)
> {
> unsigned int minor = iminor(inode);
> @@ -790,9 +782,7 @@ static const struct file_operations pp_fops = {
> .write = pp_write,
> .poll = pp_poll,
> .unlocked_ioctl = pp_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = pp_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .open = pp_open,
> .release = pp_release,
> };
> diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c
> index d74f3de74ae6..fb845f0a430b 100644
> --- a/drivers/char/tpm/tpm_vtpm_proxy.c
> +++ b/drivers/char/tpm/tpm_vtpm_proxy.c
> @@ -675,20 +675,10 @@ static long vtpmx_fops_ioctl(struct file *f, unsigned int ioctl,
> }
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vtpmx_fops_compat_ioctl(struct file *f, unsigned int ioctl,
> - unsigned long arg)
> -{
> - return vtpmx_fops_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static const struct file_operations vtpmx_fops = {
> .owner = THIS_MODULE,
> .unlocked_ioctl = vtpmx_fops_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vtpmx_fops_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .llseek = noop_llseek,
> };
>
> diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c
> index 16a7045736a9..fb934680fdd3 100644
> --- a/drivers/firewire/core-cdev.c
> +++ b/drivers/firewire/core-cdev.c
> @@ -1659,14 +1659,6 @@ static long fw_device_op_ioctl(struct file *file,
> return dispatch_ioctl(file->private_data, cmd, (void __user *)arg);
> }
>
> -#ifdef CONFIG_COMPAT
> -static long fw_device_op_compat_ioctl(struct file *file,
> - unsigned int cmd, unsigned long arg)
> -{
> - return dispatch_ioctl(file->private_data, cmd, compat_ptr(arg));
> -}
> -#endif
> -
> static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma)
> {
> struct client *client = file->private_data;
> @@ -1808,7 +1800,5 @@ const struct file_operations fw_device_ops = {
> .mmap = fw_device_op_mmap,
> .release = fw_device_op_release,
> .poll = fw_device_op_poll,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = fw_device_op_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> };
> diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
> index a746017fac17..ef4a1cd389d6 100644
> --- a/drivers/hid/usbhid/hiddev.c
> +++ b/drivers/hid/usbhid/hiddev.c
> @@ -855,13 +855,6 @@ static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> return r;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long hiddev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> -{
> - return hiddev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static const struct file_operations hiddev_fops = {
> .owner = THIS_MODULE,
> .read = hiddev_read,
> @@ -871,9 +864,7 @@ static const struct file_operations hiddev_fops = {
> .release = hiddev_release,
> .unlocked_ioctl = hiddev_ioctl,
> .fasync = hiddev_fasync,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = hiddev_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .llseek = noop_llseek,
> };
>
> diff --git a/drivers/hwtracing/stm/core.c b/drivers/hwtracing/stm/core.c
> index c7ba8acfd4d5..454da259f144 100644
> --- a/drivers/hwtracing/stm/core.c
> +++ b/drivers/hwtracing/stm/core.c
> @@ -840,23 +840,13 @@ stm_char_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> return err;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long
> -stm_char_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> -{
> - return stm_char_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> -}
> -#else
> -#define stm_char_compat_ioctl NULL
> -#endif
> -
> static const struct file_operations stm_fops = {
> .open = stm_char_open,
> .release = stm_char_release,
> .write = stm_char_write,
> .mmap = stm_char_mmap,
> .unlocked_ioctl = stm_char_ioctl,
> - .compat_ioctl = stm_char_compat_ioctl,
> + .compat_ioctl = compat_ptr_ioctl,
> .llseek = no_llseek,
> };
>
> diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
> index 87281b3695e6..cc6af92cdef0 100644
> --- a/drivers/misc/mei/main.c
> +++ b/drivers/misc/mei/main.c
> @@ -535,24 +535,6 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
> return rets;
> }
>
> -/**
> - * mei_compat_ioctl - the compat IOCTL function
> - *
> - * @file: pointer to file structure
> - * @cmd: ioctl command
> - * @data: pointer to mei message structure
> - *
> - * Return: 0 on success , <0 on error
> - */
> -#ifdef CONFIG_COMPAT
> -static long mei_compat_ioctl(struct file *file,
> - unsigned int cmd, unsigned long data)
> -{
> - return mei_ioctl(file, cmd, (unsigned long)compat_ptr(data));
> -}
> -#endif
> -
> -
> /**
> * mei_poll - the poll function
> *
> @@ -855,9 +837,7 @@ static const struct file_operations mei_fops = {
> .owner = THIS_MODULE,
> .read = mei_read,
> .unlocked_ioctl = mei_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = mei_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .open = mei_open,
> .release = mei_release,
> .write = mei_write,
> diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c
> index 947a8adbc799..265d34fa3efa 100644
> --- a/drivers/mtd/ubi/cdev.c
> +++ b/drivers/mtd/ubi/cdev.c
> @@ -1091,36 +1091,6 @@ static long ctrl_cdev_ioctl(struct file *file, unsigned int cmd,
> return err;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vol_cdev_compat_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> -{
> - unsigned long translated_arg = (unsigned long)compat_ptr(arg);
> -
> - return vol_cdev_ioctl(file, cmd, translated_arg);
> -}
> -
> -static long ubi_cdev_compat_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> -{
> - unsigned long translated_arg = (unsigned long)compat_ptr(arg);
> -
> - return ubi_cdev_ioctl(file, cmd, translated_arg);
> -}
> -
> -static long ctrl_cdev_compat_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> -{
> - unsigned long translated_arg = (unsigned long)compat_ptr(arg);
> -
> - return ctrl_cdev_ioctl(file, cmd, translated_arg);
> -}
> -#else
> -#define vol_cdev_compat_ioctl NULL
> -#define ubi_cdev_compat_ioctl NULL
> -#define ctrl_cdev_compat_ioctl NULL
> -#endif
> -
> /* UBI volume character device operations */
> const struct file_operations ubi_vol_cdev_operations = {
> .owner = THIS_MODULE,
> @@ -1131,7 +1101,7 @@ const struct file_operations ubi_vol_cdev_operations = {
> .write = vol_cdev_write,
> .fsync = vol_cdev_fsync,
> .unlocked_ioctl = vol_cdev_ioctl,
> - .compat_ioctl = vol_cdev_compat_ioctl,
> + .compat_ioctl = compat_ptr_ioctl,
> };
>
> /* UBI character device operations */
> @@ -1139,13 +1109,13 @@ const struct file_operations ubi_cdev_operations = {
> .owner = THIS_MODULE,
> .llseek = no_llseek,
> .unlocked_ioctl = ubi_cdev_ioctl,
> - .compat_ioctl = ubi_cdev_compat_ioctl,
> + .compat_ioctl = compat_ptr_ioctl,
> };
>
> /* UBI control character device operations */
> const struct file_operations ubi_ctrl_cdev_operations = {
> .owner = THIS_MODULE,
> .unlocked_ioctl = ctrl_cdev_ioctl,
> - .compat_ioctl = ctrl_cdev_compat_ioctl,
> + .compat_ioctl = compat_ptr_ioctl,
> .llseek = no_llseek,
> };
> diff --git a/drivers/net/tap.c b/drivers/net/tap.c
> index 2ea9b4976f4a..ebe425e65992 100644
> --- a/drivers/net/tap.c
> +++ b/drivers/net/tap.c
> @@ -1123,14 +1123,6 @@ static long tap_ioctl(struct file *file, unsigned int cmd,
> }
> }
>
> -#ifdef CONFIG_COMPAT
> -static long tap_compat_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> -{
> - return tap_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static const struct file_operations tap_fops = {
> .owner = THIS_MODULE,
> .open = tap_open,
> @@ -1140,9 +1132,7 @@ static const struct file_operations tap_fops = {
> .poll = tap_poll,
> .llseek = no_llseek,
> .unlocked_ioctl = tap_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = tap_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> };
>
> static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff *xdp)
> diff --git a/drivers/staging/pi433/pi433_if.c b/drivers/staging/pi433/pi433_if.c
> index b2314636dc89..ab7dfc7c2917 100644
> --- a/drivers/staging/pi433/pi433_if.c
> +++ b/drivers/staging/pi433/pi433_if.c
> @@ -935,16 +935,6 @@ pi433_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> return retval;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long
> -pi433_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> -{
> - return pi433_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
> -}
> -#else
> -#define pi433_compat_ioctl NULL
> -#endif /* CONFIG_COMPAT */
> -
> /*-------------------------------------------------------------------------*/
>
> static int pi433_open(struct inode *inode, struct file *filp)
> @@ -1101,7 +1091,7 @@ static const struct file_operations pi433_fops = {
> .write = pi433_write,
> .read = pi433_read,
> .unlocked_ioctl = pi433_ioctl,
> - .compat_ioctl = pi433_compat_ioctl,
> + .compat_ioctl = compat_ptr_ioctl,
> .open = pi433_open,
> .release = pi433_release,
> .llseek = no_llseek,
> diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
> index fa783531ee88..d75052b36584 100644
> --- a/drivers/usb/core/devio.c
> +++ b/drivers/usb/core/devio.c
> @@ -2568,18 +2568,6 @@ static long usbdev_ioctl(struct file *file, unsigned int cmd,
> return ret;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long usbdev_compat_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> -{
> - int ret;
> -
> - ret = usbdev_do_ioctl(file, cmd, compat_ptr(arg));
> -
> - return ret;
> -}
> -#endif
> -
> /* No kernel lock - fine */
> static __poll_t usbdev_poll(struct file *file,
> struct poll_table_struct *wait)
> @@ -2603,9 +2591,7 @@ const struct file_operations usbdev_file_operations = {
> .read = usbdev_read,
> .poll = usbdev_poll,
> .unlocked_ioctl = usbdev_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = usbdev_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .mmap = usbdev_mmap,
> .open = usbdev_open,
> .release = usbdev_release,
> diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c
> index a3030cdf3c18..a5efe82584a5 100644
> --- a/drivers/vfio/vfio.c
> +++ b/drivers/vfio/vfio.c
> @@ -1200,15 +1200,6 @@ static long vfio_fops_unl_ioctl(struct file *filep,
> return ret;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vfio_fops_compat_ioctl(struct file *filep,
> - unsigned int cmd, unsigned long arg)
> -{
> - arg = (unsigned long)compat_ptr(arg);
> - return vfio_fops_unl_ioctl(filep, cmd, arg);
> -}
> -#endif /* CONFIG_COMPAT */
> -
> static int vfio_fops_open(struct inode *inode, struct file *filep)
> {
> struct vfio_container *container;
> @@ -1291,9 +1282,7 @@ static const struct file_operations vfio_fops = {
> .read = vfio_fops_read,
> .write = vfio_fops_write,
> .unlocked_ioctl = vfio_fops_unl_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vfio_fops_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .mmap = vfio_fops_mmap,
> };
>
> @@ -1572,15 +1561,6 @@ static long vfio_group_fops_unl_ioctl(struct file *filep,
> return ret;
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vfio_group_fops_compat_ioctl(struct file *filep,
> - unsigned int cmd, unsigned long arg)
> -{
> - arg = (unsigned long)compat_ptr(arg);
> - return vfio_group_fops_unl_ioctl(filep, cmd, arg);
> -}
> -#endif /* CONFIG_COMPAT */
> -
> static int vfio_group_fops_open(struct inode *inode, struct file *filep)
> {
> struct vfio_group *group;
> @@ -1636,9 +1616,7 @@ static int vfio_group_fops_release(struct inode *inode, struct file *filep)
> static const struct file_operations vfio_group_fops = {
> .owner = THIS_MODULE,
> .unlocked_ioctl = vfio_group_fops_unl_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vfio_group_fops_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .open = vfio_group_fops_open,
> .release = vfio_group_fops_release,
> };
> @@ -1703,24 +1681,13 @@ static int vfio_device_fops_mmap(struct file *filep, struct vm_area_struct *vma)
> return device->ops->mmap(device->device_data, vma);
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vfio_device_fops_compat_ioctl(struct file *filep,
> - unsigned int cmd, unsigned long arg)
> -{
> - arg = (unsigned long)compat_ptr(arg);
> - return vfio_device_fops_unl_ioctl(filep, cmd, arg);
> -}
> -#endif /* CONFIG_COMPAT */
> -
> static const struct file_operations vfio_device_fops = {
> .owner = THIS_MODULE,
> .release = vfio_device_fops_release,
> .read = vfio_device_fops_read,
> .write = vfio_device_fops_write,
> .unlocked_ioctl = vfio_device_fops_unl_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vfio_device_fops_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .mmap = vfio_device_fops_mmap,
> };
>
> diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
> index df51a35cf537..1642b3573230 100644
> --- a/drivers/vhost/net.c
> +++ b/drivers/vhost/net.c
> @@ -1765,14 +1765,6 @@ static long vhost_net_ioctl(struct file *f, unsigned int ioctl,
> }
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vhost_net_compat_ioctl(struct file *f, unsigned int ioctl,
> - unsigned long arg)
> -{
> - return vhost_net_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static ssize_t vhost_net_chr_read_iter(struct kiocb *iocb, struct iov_iter *to)
> {
> struct file *file = iocb->ki_filp;
> @@ -1808,9 +1800,7 @@ static const struct file_operations vhost_net_fops = {
> .write_iter = vhost_net_chr_write_iter,
> .poll = vhost_net_chr_poll,
> .unlocked_ioctl = vhost_net_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vhost_net_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .open = vhost_net_open,
> .llseek = noop_llseek,
> };
> diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c
> index 618fb6461017..f9b14c39d89b 100644
> --- a/drivers/vhost/scsi.c
> +++ b/drivers/vhost/scsi.c
> @@ -1721,21 +1721,11 @@ vhost_scsi_ioctl(struct file *f,
> }
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vhost_scsi_compat_ioctl(struct file *f, unsigned int ioctl,
> - unsigned long arg)
> -{
> - return vhost_scsi_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static const struct file_operations vhost_scsi_fops = {
> .owner = THIS_MODULE,
> .release = vhost_scsi_release,
> .unlocked_ioctl = vhost_scsi_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vhost_scsi_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .open = vhost_scsi_open,
> .llseek = noop_llseek,
> };
> diff --git a/drivers/vhost/test.c b/drivers/vhost/test.c
> index 40589850eb33..61d4d98c8f70 100644
> --- a/drivers/vhost/test.c
> +++ b/drivers/vhost/test.c
> @@ -298,21 +298,11 @@ static long vhost_test_ioctl(struct file *f, unsigned int ioctl,
> }
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vhost_test_compat_ioctl(struct file *f, unsigned int ioctl,
> - unsigned long arg)
> -{
> - return vhost_test_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static const struct file_operations vhost_test_fops = {
> .owner = THIS_MODULE,
> .release = vhost_test_release,
> .unlocked_ioctl = vhost_test_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vhost_test_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .open = vhost_test_open,
> .llseek = noop_llseek,
> };
> diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
> index bb5fc0e9fbc2..9a86202678b6 100644
> --- a/drivers/vhost/vsock.c
> +++ b/drivers/vhost/vsock.c
> @@ -716,23 +716,13 @@ static long vhost_vsock_dev_ioctl(struct file *f, unsigned int ioctl,
> }
> }
>
> -#ifdef CONFIG_COMPAT
> -static long vhost_vsock_dev_compat_ioctl(struct file *f, unsigned int ioctl,
> - unsigned long arg)
> -{
> - return vhost_vsock_dev_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static const struct file_operations vhost_vsock_fops = {
> .owner = THIS_MODULE,
> .open = vhost_vsock_dev_open,
> .release = vhost_vsock_dev_release,
> .llseek = noop_llseek,
> .unlocked_ioctl = vhost_vsock_dev_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = vhost_vsock_dev_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> };
>
> static struct miscdevice vhost_vsock_misc = {
> diff --git a/fs/fat/file.c b/fs/fat/file.c
> index b3bed32946b1..f173d9261115 100644
> --- a/fs/fat/file.c
> +++ b/fs/fat/file.c
> @@ -171,15 +171,6 @@ long fat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> }
> }
>
> -#ifdef CONFIG_COMPAT
> -static long fat_generic_compat_ioctl(struct file *filp, unsigned int cmd,
> - unsigned long arg)
> -
> -{
> - return fat_generic_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
> -}
> -#endif
> -
> static int fat_file_release(struct inode *inode, struct file *filp)
> {
> if ((filp->f_mode & FMODE_WRITE) &&
> @@ -209,9 +200,7 @@ const struct file_operations fat_file_operations = {
> .mmap = generic_file_mmap,
> .release = fat_file_release,
> .unlocked_ioctl = fat_generic_ioctl,
> -#ifdef CONFIG_COMPAT
> - .compat_ioctl = fat_generic_compat_ioctl,
> -#endif
> + .compat_ioctl = compat_ptr_ioctl,
> .fsync = fat_file_fsync,
> .splice_read = generic_file_splice_read,
> .splice_write = iter_file_splice_write,
> --
> 2.20.0
^ permalink raw reply
* [PATCH] HID: intel-ish-hid: Add Comet Lake PCI device ID
From: Srinivas Pandruvada @ 2019-04-20 2:00 UTC (permalink / raw)
To: jikos, benjamin.tissoires; +Cc: linux-input, linux-kernel, Srinivas Pandruvada
Added Comet Lake PCI device ID to the supported device list.
Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
---
drivers/hid/intel-ish-hid/ipc/hw-ish.h | 1 +
drivers/hid/intel-ish-hid/ipc/pci-ish.c | 1 +
2 files changed, 2 insertions(+)
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
index 08a8327dfd22..523c0cbd44a4 100644
--- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h
+++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
@@ -31,6 +31,7 @@
#define CNL_H_DEVICE_ID 0xA37C
#define ICL_MOBILE_DEVICE_ID 0x34FC
#define SPT_H_DEVICE_ID 0xA135
+#define CML_LP_DEVICE_ID 0x02FC
#define REVISION_ID_CHT_A0 0x6
#define REVISION_ID_CHT_Ax_SI 0x0
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
index a6e1ee744f4d..ac0a179daf23 100644
--- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -40,6 +40,7 @@ static const struct pci_device_id ish_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CML_LP_DEVICE_ID)},
{0, }
};
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
--
2.17.2
^ permalink raw reply related
* RE: [PATCH v3 09/26] compat_ioctl: move drivers to compat_ptr_ioctl
From: Winkler, Tomas @ 2019-04-20 8:03 UTC (permalink / raw)
To: Arnd Bergmann
Cc: linux-usb@vger.kernel.org, kvm@vger.kernel.org,
Michael S. Tsirkin, Alexander Shishkin, Jason Wang,
Jarkko Sakkinen, virtualization@lists.linux-foundation.org,
Benjamin Tissoires, linux-mtd@lists.infradead.org, Peter Huewe,
linux1394-devel@lists.sourceforge.net,
linux-stm32@st-md-mailman.stormreply.com,
devel@driverdev.osuosl.org, Marek Vasut, y2038@lists.linaro.org,
Richard Weinberger
In-Reply-To: <20190419191633-mutt-send-email-mst@kernel.org>
>
> On Tue, Apr 16, 2019 at 10:19:47PM +0200, Arnd Bergmann wrote:
> > Each of these drivers has a copy of the same trivial helper function
> > to convert the pointer argument and then call the native ioctl handler.
> >
> > We now have a generic implementation of that, so use it.
> >
> > Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> > Reviewed-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
> > Reviewed-by: Jason Gunthorpe <jgg@mellanox.com>
> > Signed-off-by: Arnd Bergmann <arnd@arndb.de>
>
> Acked-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Tomas Winkler <tomas.winkler@intel.com>
>
> > ---
> > drivers/char/ppdev.c | 12 +---------
> > drivers/char/tpm/tpm_vtpm_proxy.c | 12 +---------
> > drivers/firewire/core-cdev.c | 12 +---------
> > drivers/hid/usbhid/hiddev.c | 11 +--------
> > drivers/hwtracing/stm/core.c | 12 +---------
> > drivers/misc/mei/main.c | 22 +----------------
> > drivers/mtd/ubi/cdev.c | 36 +++-------------------------
> > drivers/net/tap.c | 12 +---------
> > drivers/staging/pi433/pi433_if.c | 12 +---------
> > drivers/usb/core/devio.c | 16 +------------
> > drivers/vfio/vfio.c | 39 +++----------------------------
> > drivers/vhost/net.c | 12 +---------
> > drivers/vhost/scsi.c | 12 +---------
> > drivers/vhost/test.c | 12 +---------
> > drivers/vhost/vsock.c | 12 +---------
> > fs/fat/file.c | 13 +----------
> > 16 files changed, 20 insertions(+), 237 deletions(-)
> >
> > diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c index
> > 1ae77b41050a..e96c8d9623e0 100644
> > --- a/drivers/char/ppdev.c
> > +++ b/drivers/char/ppdev.c
> > @@ -674,14 +674,6 @@ static long pp_ioctl(struct file *file, unsigned int
> cmd, unsigned long arg)
> > return ret;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long pp_compat_ioctl(struct file *file, unsigned int cmd,
> > - unsigned long arg)
> > -{
> > - return pp_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static int pp_open(struct inode *inode, struct file *file) {
> > unsigned int minor = iminor(inode);
> > @@ -790,9 +782,7 @@ static const struct file_operations pp_fops = {
> > .write = pp_write,
> > .poll = pp_poll,
> > .unlocked_ioctl = pp_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = pp_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .open = pp_open,
> > .release = pp_release,
> > };
> > diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c
> > b/drivers/char/tpm/tpm_vtpm_proxy.c
> > index d74f3de74ae6..fb845f0a430b 100644
> > --- a/drivers/char/tpm/tpm_vtpm_proxy.c
> > +++ b/drivers/char/tpm/tpm_vtpm_proxy.c
> > @@ -675,20 +675,10 @@ static long vtpmx_fops_ioctl(struct file *f, unsigned
> int ioctl,
> > }
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vtpmx_fops_compat_ioctl(struct file *f, unsigned int ioctl,
> > - unsigned long arg)
> > -{
> > - return vtpmx_fops_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static const struct file_operations vtpmx_fops = {
> > .owner = THIS_MODULE,
> > .unlocked_ioctl = vtpmx_fops_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vtpmx_fops_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .llseek = noop_llseek,
> > };
> >
> > diff --git a/drivers/firewire/core-cdev.c
> > b/drivers/firewire/core-cdev.c index 16a7045736a9..fb934680fdd3 100644
> > --- a/drivers/firewire/core-cdev.c
> > +++ b/drivers/firewire/core-cdev.c
> > @@ -1659,14 +1659,6 @@ static long fw_device_op_ioctl(struct file *file,
> > return dispatch_ioctl(file->private_data, cmd, (void __user *)arg);
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long fw_device_op_compat_ioctl(struct file *file,
> > - unsigned int cmd, unsigned long arg)
> > -{
> > - return dispatch_ioctl(file->private_data, cmd, compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static int fw_device_op_mmap(struct file *file, struct vm_area_struct
> > *vma) {
> > struct client *client = file->private_data; @@ -1808,7 +1800,5 @@
> > const struct file_operations fw_device_ops = {
> > .mmap = fw_device_op_mmap,
> > .release = fw_device_op_release,
> > .poll = fw_device_op_poll,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = fw_device_op_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > };
> > diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
> > index a746017fac17..ef4a1cd389d6 100644
> > --- a/drivers/hid/usbhid/hiddev.c
> > +++ b/drivers/hid/usbhid/hiddev.c
> > @@ -855,13 +855,6 @@ static long hiddev_ioctl(struct file *file, unsigned int
> cmd, unsigned long arg)
> > return r;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long hiddev_compat_ioctl(struct file *file, unsigned int cmd,
> > unsigned long arg) -{
> > - return hiddev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static const struct file_operations hiddev_fops = {
> > .owner = THIS_MODULE,
> > .read = hiddev_read,
> > @@ -871,9 +864,7 @@ static const struct file_operations hiddev_fops = {
> > .release = hiddev_release,
> > .unlocked_ioctl = hiddev_ioctl,
> > .fasync = hiddev_fasync,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = hiddev_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .llseek = noop_llseek,
> > };
> >
> > diff --git a/drivers/hwtracing/stm/core.c
> > b/drivers/hwtracing/stm/core.c index c7ba8acfd4d5..454da259f144 100644
> > --- a/drivers/hwtracing/stm/core.c
> > +++ b/drivers/hwtracing/stm/core.c
> > @@ -840,23 +840,13 @@ stm_char_ioctl(struct file *file, unsigned int cmd,
> unsigned long arg)
> > return err;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long
> > -stm_char_compat_ioctl(struct file *file, unsigned int cmd, unsigned
> > long arg) -{
> > - return stm_char_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> > -}
> > -#else
> > -#define stm_char_compat_ioctl NULL
> > -#endif
> > -
> > static const struct file_operations stm_fops = {
> > .open = stm_char_open,
> > .release = stm_char_release,
> > .write = stm_char_write,
> > .mmap = stm_char_mmap,
> > .unlocked_ioctl = stm_char_ioctl,
> > - .compat_ioctl = stm_char_compat_ioctl,
> > + .compat_ioctl = compat_ptr_ioctl,
> > .llseek = no_llseek,
> > };
> >
> > diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index
> > 87281b3695e6..cc6af92cdef0 100644
> > --- a/drivers/misc/mei/main.c
> > +++ b/drivers/misc/mei/main.c
> > @@ -535,24 +535,6 @@ static long mei_ioctl(struct file *file, unsigned int
> cmd, unsigned long data)
> > return rets;
> > }
> >
> > -/**
> > - * mei_compat_ioctl - the compat IOCTL function
> > - *
> > - * @file: pointer to file structure
> > - * @cmd: ioctl command
> > - * @data: pointer to mei message structure
> > - *
> > - * Return: 0 on success , <0 on error
> > - */
> > -#ifdef CONFIG_COMPAT
> > -static long mei_compat_ioctl(struct file *file,
> > - unsigned int cmd, unsigned long data)
> > -{
> > - return mei_ioctl(file, cmd, (unsigned long)compat_ptr(data));
> > -}
> > -#endif
> > -
> > -
> > /**
> > * mei_poll - the poll function
> > *
> > @@ -855,9 +837,7 @@ static const struct file_operations mei_fops = {
> > .owner = THIS_MODULE,
> > .read = mei_read,
> > .unlocked_ioctl = mei_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = mei_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .open = mei_open,
> > .release = mei_release,
> > .write = mei_write,
> > diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c index
> > 947a8adbc799..265d34fa3efa 100644
> > --- a/drivers/mtd/ubi/cdev.c
> > +++ b/drivers/mtd/ubi/cdev.c
> > @@ -1091,36 +1091,6 @@ static long ctrl_cdev_ioctl(struct file *file,
> unsigned int cmd,
> > return err;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vol_cdev_compat_ioctl(struct file *file, unsigned int cmd,
> > - unsigned long arg)
> > -{
> > - unsigned long translated_arg = (unsigned long)compat_ptr(arg);
> > -
> > - return vol_cdev_ioctl(file, cmd, translated_arg);
> > -}
> > -
> > -static long ubi_cdev_compat_ioctl(struct file *file, unsigned int cmd,
> > - unsigned long arg)
> > -{
> > - unsigned long translated_arg = (unsigned long)compat_ptr(arg);
> > -
> > - return ubi_cdev_ioctl(file, cmd, translated_arg);
> > -}
> > -
> > -static long ctrl_cdev_compat_ioctl(struct file *file, unsigned int cmd,
> > - unsigned long arg)
> > -{
> > - unsigned long translated_arg = (unsigned long)compat_ptr(arg);
> > -
> > - return ctrl_cdev_ioctl(file, cmd, translated_arg);
> > -}
> > -#else
> > -#define vol_cdev_compat_ioctl NULL
> > -#define ubi_cdev_compat_ioctl NULL
> > -#define ctrl_cdev_compat_ioctl NULL
> > -#endif
> > -
> > /* UBI volume character device operations */ const struct
> > file_operations ubi_vol_cdev_operations = {
> > .owner = THIS_MODULE,
> > @@ -1131,7 +1101,7 @@ const struct file_operations
> ubi_vol_cdev_operations = {
> > .write = vol_cdev_write,
> > .fsync = vol_cdev_fsync,
> > .unlocked_ioctl = vol_cdev_ioctl,
> > - .compat_ioctl = vol_cdev_compat_ioctl,
> > + .compat_ioctl = compat_ptr_ioctl,
> > };
> >
> > /* UBI character device operations */ @@ -1139,13 +1109,13 @@ const
> > struct file_operations ubi_cdev_operations = {
> > .owner = THIS_MODULE,
> > .llseek = no_llseek,
> > .unlocked_ioctl = ubi_cdev_ioctl,
> > - .compat_ioctl = ubi_cdev_compat_ioctl,
> > + .compat_ioctl = compat_ptr_ioctl,
> > };
> >
> > /* UBI control character device operations */ const struct
> > file_operations ubi_ctrl_cdev_operations = {
> > .owner = THIS_MODULE,
> > .unlocked_ioctl = ctrl_cdev_ioctl,
> > - .compat_ioctl = ctrl_cdev_compat_ioctl,
> > + .compat_ioctl = compat_ptr_ioctl,
> > .llseek = no_llseek,
> > };
> > diff --git a/drivers/net/tap.c b/drivers/net/tap.c index
> > 2ea9b4976f4a..ebe425e65992 100644
> > --- a/drivers/net/tap.c
> > +++ b/drivers/net/tap.c
> > @@ -1123,14 +1123,6 @@ static long tap_ioctl(struct file *file, unsigned int
> cmd,
> > }
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long tap_compat_ioctl(struct file *file, unsigned int cmd,
> > - unsigned long arg)
> > -{
> > - return tap_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static const struct file_operations tap_fops = {
> > .owner = THIS_MODULE,
> > .open = tap_open,
> > @@ -1140,9 +1132,7 @@ static const struct file_operations tap_fops = {
> > .poll = tap_poll,
> > .llseek = no_llseek,
> > .unlocked_ioctl = tap_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = tap_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > };
> >
> > static int tap_get_user_xdp(struct tap_queue *q, struct xdp_buff
> > *xdp) diff --git a/drivers/staging/pi433/pi433_if.c
> > b/drivers/staging/pi433/pi433_if.c
> > index b2314636dc89..ab7dfc7c2917 100644
> > --- a/drivers/staging/pi433/pi433_if.c
> > +++ b/drivers/staging/pi433/pi433_if.c
> > @@ -935,16 +935,6 @@ pi433_ioctl(struct file *filp, unsigned int cmd,
> unsigned long arg)
> > return retval;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long
> > -pi433_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long
> > arg) -{
> > - return pi433_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
> > -}
> > -#else
> > -#define pi433_compat_ioctl NULL
> > -#endif /* CONFIG_COMPAT */
> > -
> >
> > /*--------------------------------------------------------------------
> > -----*/
> >
> > static int pi433_open(struct inode *inode, struct file *filp) @@
> > -1101,7 +1091,7 @@ static const struct file_operations pi433_fops = {
> > .write = pi433_write,
> > .read = pi433_read,
> > .unlocked_ioctl = pi433_ioctl,
> > - .compat_ioctl = pi433_compat_ioctl,
> > + .compat_ioctl = compat_ptr_ioctl,
> > .open = pi433_open,
> > .release = pi433_release,
> > .llseek = no_llseek,
> > diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index
> > fa783531ee88..d75052b36584 100644
> > --- a/drivers/usb/core/devio.c
> > +++ b/drivers/usb/core/devio.c
> > @@ -2568,18 +2568,6 @@ static long usbdev_ioctl(struct file *file, unsigned
> int cmd,
> > return ret;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long usbdev_compat_ioctl(struct file *file, unsigned int cmd,
> > - unsigned long arg)
> > -{
> > - int ret;
> > -
> > - ret = usbdev_do_ioctl(file, cmd, compat_ptr(arg));
> > -
> > - return ret;
> > -}
> > -#endif
> > -
> > /* No kernel lock - fine */
> > static __poll_t usbdev_poll(struct file *file,
> > struct poll_table_struct *wait)
> > @@ -2603,9 +2591,7 @@ const struct file_operations usbdev_file_operations
> = {
> > .read = usbdev_read,
> > .poll = usbdev_poll,
> > .unlocked_ioctl = usbdev_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = usbdev_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .mmap = usbdev_mmap,
> > .open = usbdev_open,
> > .release = usbdev_release,
> > diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c index
> > a3030cdf3c18..a5efe82584a5 100644
> > --- a/drivers/vfio/vfio.c
> > +++ b/drivers/vfio/vfio.c
> > @@ -1200,15 +1200,6 @@ static long vfio_fops_unl_ioctl(struct file *filep,
> > return ret;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vfio_fops_compat_ioctl(struct file *filep,
> > - unsigned int cmd, unsigned long arg)
> > -{
> > - arg = (unsigned long)compat_ptr(arg);
> > - return vfio_fops_unl_ioctl(filep, cmd, arg);
> > -}
> > -#endif /* CONFIG_COMPAT */
> > -
> > static int vfio_fops_open(struct inode *inode, struct file *filep) {
> > struct vfio_container *container;
> > @@ -1291,9 +1282,7 @@ static const struct file_operations vfio_fops = {
> > .read = vfio_fops_read,
> > .write = vfio_fops_write,
> > .unlocked_ioctl = vfio_fops_unl_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vfio_fops_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .mmap = vfio_fops_mmap,
> > };
> >
> > @@ -1572,15 +1561,6 @@ static long vfio_group_fops_unl_ioctl(struct file
> *filep,
> > return ret;
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vfio_group_fops_compat_ioctl(struct file *filep,
> > - unsigned int cmd, unsigned long arg)
> > -{
> > - arg = (unsigned long)compat_ptr(arg);
> > - return vfio_group_fops_unl_ioctl(filep, cmd, arg);
> > -}
> > -#endif /* CONFIG_COMPAT */
> > -
> > static int vfio_group_fops_open(struct inode *inode, struct file
> > *filep) {
> > struct vfio_group *group;
> > @@ -1636,9 +1616,7 @@ static int vfio_group_fops_release(struct inode
> > *inode, struct file *filep) static const struct file_operations vfio_group_fops =
> {
> > .owner = THIS_MODULE,
> > .unlocked_ioctl = vfio_group_fops_unl_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vfio_group_fops_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .open = vfio_group_fops_open,
> > .release = vfio_group_fops_release,
> > };
> > @@ -1703,24 +1681,13 @@ static int vfio_device_fops_mmap(struct file
> *filep, struct vm_area_struct *vma)
> > return device->ops->mmap(device->device_data, vma); }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vfio_device_fops_compat_ioctl(struct file *filep,
> > - unsigned int cmd, unsigned long arg)
> > -{
> > - arg = (unsigned long)compat_ptr(arg);
> > - return vfio_device_fops_unl_ioctl(filep, cmd, arg);
> > -}
> > -#endif /* CONFIG_COMPAT */
> > -
> > static const struct file_operations vfio_device_fops = {
> > .owner = THIS_MODULE,
> > .release = vfio_device_fops_release,
> > .read = vfio_device_fops_read,
> > .write = vfio_device_fops_write,
> > .unlocked_ioctl = vfio_device_fops_unl_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vfio_device_fops_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .mmap = vfio_device_fops_mmap,
> > };
> >
> > diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index
> > df51a35cf537..1642b3573230 100644
> > --- a/drivers/vhost/net.c
> > +++ b/drivers/vhost/net.c
> > @@ -1765,14 +1765,6 @@ static long vhost_net_ioctl(struct file *f, unsigned
> int ioctl,
> > }
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vhost_net_compat_ioctl(struct file *f, unsigned int ioctl,
> > - unsigned long arg)
> > -{
> > - return vhost_net_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static ssize_t vhost_net_chr_read_iter(struct kiocb *iocb, struct
> > iov_iter *to) {
> > struct file *file = iocb->ki_filp;
> > @@ -1808,9 +1800,7 @@ static const struct file_operations vhost_net_fops =
> {
> > .write_iter = vhost_net_chr_write_iter,
> > .poll = vhost_net_chr_poll,
> > .unlocked_ioctl = vhost_net_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vhost_net_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .open = vhost_net_open,
> > .llseek = noop_llseek,
> > };
> > diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c index
> > 618fb6461017..f9b14c39d89b 100644
> > --- a/drivers/vhost/scsi.c
> > +++ b/drivers/vhost/scsi.c
> > @@ -1721,21 +1721,11 @@ vhost_scsi_ioctl(struct file *f,
> > }
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vhost_scsi_compat_ioctl(struct file *f, unsigned int ioctl,
> > - unsigned long arg)
> > -{
> > - return vhost_scsi_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static const struct file_operations vhost_scsi_fops = {
> > .owner = THIS_MODULE,
> > .release = vhost_scsi_release,
> > .unlocked_ioctl = vhost_scsi_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vhost_scsi_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .open = vhost_scsi_open,
> > .llseek = noop_llseek,
> > };
> > diff --git a/drivers/vhost/test.c b/drivers/vhost/test.c index
> > 40589850eb33..61d4d98c8f70 100644
> > --- a/drivers/vhost/test.c
> > +++ b/drivers/vhost/test.c
> > @@ -298,21 +298,11 @@ static long vhost_test_ioctl(struct file *f, unsigned
> int ioctl,
> > }
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vhost_test_compat_ioctl(struct file *f, unsigned int ioctl,
> > - unsigned long arg)
> > -{
> > - return vhost_test_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static const struct file_operations vhost_test_fops = {
> > .owner = THIS_MODULE,
> > .release = vhost_test_release,
> > .unlocked_ioctl = vhost_test_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vhost_test_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .open = vhost_test_open,
> > .llseek = noop_llseek,
> > };
> > diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c index
> > bb5fc0e9fbc2..9a86202678b6 100644
> > --- a/drivers/vhost/vsock.c
> > +++ b/drivers/vhost/vsock.c
> > @@ -716,23 +716,13 @@ static long vhost_vsock_dev_ioctl(struct file *f,
> unsigned int ioctl,
> > }
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long vhost_vsock_dev_compat_ioctl(struct file *f, unsigned int ioctl,
> > - unsigned long arg)
> > -{
> > - return vhost_vsock_dev_ioctl(f, ioctl, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static const struct file_operations vhost_vsock_fops = {
> > .owner = THIS_MODULE,
> > .open = vhost_vsock_dev_open,
> > .release = vhost_vsock_dev_release,
> > .llseek = noop_llseek,
> > .unlocked_ioctl = vhost_vsock_dev_ioctl, -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = vhost_vsock_dev_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > };
> >
> > static struct miscdevice vhost_vsock_misc = { diff --git
> > a/fs/fat/file.c b/fs/fat/file.c index b3bed32946b1..f173d9261115
> > 100644
> > --- a/fs/fat/file.c
> > +++ b/fs/fat/file.c
> > @@ -171,15 +171,6 @@ long fat_generic_ioctl(struct file *filp, unsigned int
> cmd, unsigned long arg)
> > }
> > }
> >
> > -#ifdef CONFIG_COMPAT
> > -static long fat_generic_compat_ioctl(struct file *filp, unsigned int cmd,
> > - unsigned long arg)
> > -
> > -{
> > - return fat_generic_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
> > -}
> > -#endif
> > -
> > static int fat_file_release(struct inode *inode, struct file *filp)
> > {
> > if ((filp->f_mode & FMODE_WRITE) &&
> > @@ -209,9 +200,7 @@ const struct file_operations fat_file_operations = {
> > .mmap = generic_file_mmap,
> > .release = fat_file_release,
> > .unlocked_ioctl = fat_generic_ioctl,
> > -#ifdef CONFIG_COMPAT
> > - .compat_ioctl = fat_generic_compat_ioctl,
> > -#endif
> > + .compat_ioctl = compat_ptr_ioctl,
> > .fsync = fat_file_fsync,
> > .splice_read = generic_file_splice_read,
> > .splice_write = iter_file_splice_write,
> > --
> > 2.20.0
^ permalink raw reply
* [PATCH v4 1/3] dt-bindings: input: add GPIO controllable vibrator
From: Luca Weiss @ 2019-04-20 12:23 UTC (permalink / raw)
Cc: Dmitry Torokhov, Rob Herring, Mark Rutland, Mauro Carvalho Chehab,
Pascal PAILLET-LME, Coly Li, Lee Jones, Xiaotong Lu, Brian Masney,
Rob Herring, Baolin Wang, David Brown,
open list:ARM/QUALCOMM SUPPORT,
open list:INPUT KEYBOARD, MOUSE, JOYSTICK , TOUCHSCREEN...,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list, Luca Weiss
Provide a simple driver for GPIO controllable vibrators.
It will be used by the Fairphone 2.
Signed-off-by: Luca Weiss <luca@z3ntu.xyz>
---
Changes from v3:
- Convert .txt based doc to the new yaml based format
.../bindings/input/gpio-vibrator.yaml | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/gpio-vibrator.yaml
diff --git a/Documentation/devicetree/bindings/input/gpio-vibrator.yaml b/Documentation/devicetree/bindings/input/gpio-vibrator.yaml
new file mode 100644
index 000000000000..bca1b6ea07a4
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/gpio-vibrator.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/bindings/input/gpio-vibrator.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: GPIO vibrator
+
+maintainers:
+ - Luca Weiss <luca@z3ntu.xyz>
+
+description: |+
+ Registers a GPIO device as vibrator, where the on/off capability is controlled by a GPIO.
+
+properties:
+ compatible:
+ items:
+ - const: gpio-vibrator
+
+ enable-gpios:
+ maxItems: 1
+
+ vcc-supply:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Regulator that provides power
+
+required:
+ - compatible
+ - enable-gpios
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ vibrator {
+ compatible = "gpio-vibrator";
+ enable-gpios = <&msmgpio 86 GPIO_ACTIVE_HIGH>;
+ vcc-supply = <&pm8941_l18>;
+ };
--
2.21.0
^ permalink raw reply related
* [PATCH v4 2/3] Input: add a driver for GPIO controllable vibrators
From: Luca Weiss @ 2019-04-20 12:23 UTC (permalink / raw)
Cc: Dmitry Torokhov, Rob Herring, Mark Rutland, Mauro Carvalho Chehab,
Pascal PAILLET-LME, Coly Li, Lee Jones, Xiaotong Lu, Brian Masney,
Rob Herring, Baolin Wang, David Brown,
open list:ARM/QUALCOMM SUPPORT,
open list:INPUT KEYBOARD, MOUSE, JOYSTICK , TOUCHSCREEN...,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list, Luca Weiss
In-Reply-To: <20190420122333.23662-1-luca@z3ntu.xyz>
Provide a simple driver for GPIO controllable vibrators.
It will be used by the Fairphone 2.
Signed-off-by: Luca Weiss <luca@z3ntu.xyz>
---
Changes from v3:
- Apply suggestions from Stephen Boyd
drivers/input/misc/Kconfig | 12 ++
drivers/input/misc/Makefile | 1 +
drivers/input/misc/gpio-vibra.c | 209 ++++++++++++++++++++++++++++++++
3 files changed, 222 insertions(+)
create mode 100644 drivers/input/misc/gpio-vibra.c
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index e15ed1bb8558..6dfe9e2fe5b1 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -290,6 +290,18 @@ config INPUT_GPIO_DECODER
To compile this driver as a module, choose M here: the module
will be called gpio_decoder.
+config INPUT_GPIO_VIBRA
+ tristate "GPIO vibrator support"
+ depends on GPIOLIB || COMPILE_TEST
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here to get support for GPIO based vibrator devices.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module will be
+ called gpio-vibra.
+
config INPUT_IXP4XX_BEEPER
tristate "IXP4XX Beeper support"
depends on ARCH_IXP4XX
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index b936c5b1d4ac..f38ebbdb05e2 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o
obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o
obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o
obj-$(CONFIG_INPUT_GPIO_DECODER) += gpio_decoder.o
+obj-$(CONFIG_INPUT_GPIO_VIBRA) += gpio-vibra.o
obj-$(CONFIG_INPUT_HISI_POWERKEY) += hisi_powerkey.o
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o
diff --git a/drivers/input/misc/gpio-vibra.c b/drivers/input/misc/gpio-vibra.c
new file mode 100644
index 000000000000..b76c81015de9
--- /dev/null
+++ b/drivers/input/misc/gpio-vibra.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * GPIO vibrator driver
+ *
+ * Copyright (C) 2019 Luca Weiss <luca@z3ntu.xyz>
+ *
+ * Based on PWM vibrator driver:
+ * Copyright (C) 2017 Collabora Ltd.
+ *
+ * Based on previous work from:
+ * Copyright (C) 2012 Dmitry Torokhov <dmitry.torokhov@gmail.com>
+ *
+ * Based on PWM beeper driver:
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+struct gpio_vibrator {
+ struct input_dev *input;
+ struct gpio_desc *gpio;
+ struct regulator *vcc;
+
+ struct work_struct play_work;
+ bool running;
+ bool vcc_on;
+};
+
+static int gpio_vibrator_start(struct gpio_vibrator *vibrator)
+{
+ struct device *pdev = vibrator->input->dev.parent;
+ int err;
+
+ if (!vibrator->vcc_on) {
+ err = regulator_enable(vibrator->vcc);
+ if (err) {
+ dev_err(pdev, "failed to enable regulator: %d\n", err);
+ return err;
+ }
+ vibrator->vcc_on = true;
+ }
+
+ gpiod_set_value_cansleep(vibrator->gpio, 1);
+
+ return 0;
+}
+
+static void gpio_vibrator_stop(struct gpio_vibrator *vibrator)
+{
+ gpiod_set_value_cansleep(vibrator->gpio, 0);
+
+ if (vibrator->vcc_on) {
+ regulator_disable(vibrator->vcc);
+ vibrator->vcc_on = false;
+ }
+}
+
+static void gpio_vibrator_play_work(struct work_struct *work)
+{
+ struct gpio_vibrator *vibrator =
+ container_of(work, struct gpio_vibrator, play_work);
+
+ if (vibrator->running)
+ gpio_vibrator_start(vibrator);
+ else
+ gpio_vibrator_stop(vibrator);
+}
+
+static int gpio_vibrator_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct gpio_vibrator *vibrator = input_get_drvdata(dev);
+
+ int level = effect->u.rumble.strong_magnitude;
+
+ if (!level)
+ level = effect->u.rumble.weak_magnitude;
+
+ if (level)
+ vibrator->running = true;
+ else
+ vibrator->running = false;
+
+ schedule_work(&vibrator->play_work);
+
+ return 0;
+}
+
+static void gpio_vibrator_close(struct input_dev *input)
+{
+ struct gpio_vibrator *vibrator = input_get_drvdata(input);
+
+ cancel_work_sync(&vibrator->play_work);
+ gpio_vibrator_stop(vibrator);
+ vibrator->running = false;
+}
+
+static int gpio_vibrator_probe(struct platform_device *pdev)
+{
+ struct gpio_vibrator *vibrator;
+ int err;
+
+ vibrator = devm_kzalloc(&pdev->dev, sizeof(*vibrator), GFP_KERNEL);
+ if (!vibrator)
+ return -ENOMEM;
+
+ vibrator->input = devm_input_allocate_device(&pdev->dev);
+ if (!vibrator->input)
+ return -ENOMEM;
+
+ vibrator->vcc = devm_regulator_get(&pdev->dev, "vcc");
+ err = PTR_ERR_OR_ZERO(vibrator->vcc);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to request regulator: %d\n",
+ err);
+ return err;
+ }
+
+ vibrator->gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW);
+ err = PTR_ERR_OR_ZERO(vibrator->gpio);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to request main gpio: %d\n",
+ err);
+ return err;
+ }
+
+ INIT_WORK(&vibrator->play_work, gpio_vibrator_play_work);
+
+ vibrator->input->name = "gpio-vibrator";
+ vibrator->input->id.bustype = BUS_HOST;
+ vibrator->input->close = gpio_vibrator_close;
+
+ input_set_drvdata(vibrator->input, vibrator);
+ input_set_capability(vibrator->input, EV_FF, FF_RUMBLE);
+
+ err = input_ff_create_memless(vibrator->input, NULL,
+ gpio_vibrator_play_effect);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't create FF dev: %d\n", err);
+ return err;
+ }
+
+ err = input_register_device(vibrator->input);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't register input dev: %d\n", err);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, vibrator);
+
+ return 0;
+}
+
+static int __maybe_unused gpio_vibrator_suspend(struct device *dev)
+{
+ struct gpio_vibrator *vibrator = dev_get_drvdata(dev);
+
+ cancel_work_sync(&vibrator->play_work);
+ if (vibrator->running)
+ gpio_vibrator_stop(vibrator);
+
+ return 0;
+}
+
+static int __maybe_unused gpio_vibrator_resume(struct device *dev)
+{
+ struct gpio_vibrator *vibrator = dev_get_drvdata(dev);
+
+ if (vibrator->running)
+ gpio_vibrator_start(vibrator);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(gpio_vibrator_pm_ops,
+ gpio_vibrator_suspend, gpio_vibrator_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id gpio_vibra_dt_match_table[] = {
+ { .compatible = "gpio-vibrator" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, gpio_vibra_dt_match_table);
+#endif
+
+static struct platform_driver gpio_vibrator_driver = {
+ .probe = gpio_vibrator_probe,
+ .driver = {
+ .name = "gpio-vibrator",
+ .pm = &gpio_vibrator_pm_ops,
+ .of_match_table = of_match_ptr(gpio_vibra_dt_match_table),
+ },
+};
+module_platform_driver(gpio_vibrator_driver);
+
+MODULE_AUTHOR("Luca Weiss <luca@z3ntu.xy>");
+MODULE_DESCRIPTION("GPIO vibrator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-vibrator");
--
2.21.0
^ permalink raw reply related
* [PATCH v4 3/3] ARM: dts: msm8974-FP2: Add vibration motor
From: Luca Weiss @ 2019-04-20 12:23 UTC (permalink / raw)
Cc: Dmitry Torokhov, Rob Herring, Mark Rutland, Mauro Carvalho Chehab,
Pascal PAILLET-LME, Coly Li, Lee Jones, Xiaotong Lu, Brian Masney,
Rob Herring, Baolin Wang, David Brown,
open list:ARM/QUALCOMM SUPPORT,
open list:INPUT KEYBOARD, MOUSE, JOYSTICK , TOUCHSCREEN...,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list, Luca Weiss
In-Reply-To: <20190420122333.23662-1-luca@z3ntu.xyz>
Signed-off-by: Luca Weiss <luca@z3ntu.xyz>
---
arch/arm/boot/dts/qcom-msm8974-fairphone-fp2.dts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/qcom-msm8974-fairphone-fp2.dts b/arch/arm/boot/dts/qcom-msm8974-fairphone-fp2.dts
index 643c57f84818..bf402ae39226 100644
--- a/arch/arm/boot/dts/qcom-msm8974-fairphone-fp2.dts
+++ b/arch/arm/boot/dts/qcom-msm8974-fairphone-fp2.dts
@@ -50,6 +50,12 @@
};
};
+ vibrator {
+ compatible = "gpio-vibrator";
+ enable-gpios = <&msmgpio 86 GPIO_ACTIVE_HIGH>;
+ vcc-supply = <&pm8941_l18>;
+ };
+
smd {
rpm {
rpm_requests {
--
2.21.0
^ permalink raw reply related
* [PATCH 0/3] Apple iBridge support
From: Ronald Tschalär @ 2019-04-22 3:12 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones
Cc: linux-input, linux-iio, linux-kernel
2016 and 2017 MacBook Pro's have a T1 chip that drives the Touch Bar,
ambient light sensor, webcam, and fingerprint sensor; this shows up
as an iBridge USB device in the system. These patches provide initial
support for the Touch Bar and ALS - the webcam is already handled by
existing drivers, and no information is currently known on how to access
the fingerprint sensor (other than it's apparently via one of the extra
interfaces available in the OS X USB configuration).
One thing of note here is that both the ALS and (some of) the Touch Bar
functionality are exposed via the same USB interface (and hence same
hid_device), so both drivers need to share this device. This
necessitated creating a demux hid driver in the mfd driver to which
multiple hid devices can be attached, and implied not being able to make
use of the existing hid-sensor-als driver.
Ronald Tschalär (3):
mfd: apple-ibridge: Add Apple iBridge MFD driver.
HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's.
iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
drivers/hid/Kconfig | 10 +
drivers/hid/Makefile | 1 +
drivers/hid/apple-ib-tb.c | 1288 +++++++++++++++++++++++++++++
drivers/iio/light/Kconfig | 12 +
drivers/iio/light/Makefile | 1 +
drivers/iio/light/apple-ib-als.c | 694 ++++++++++++++++
drivers/mfd/Kconfig | 15 +
drivers/mfd/Makefile | 1 +
drivers/mfd/apple-ibridge.c | 883 ++++++++++++++++++++
include/linux/mfd/apple-ibridge.h | 39 +
10 files changed, 2944 insertions(+)
create mode 100644 drivers/hid/apple-ib-tb.c
create mode 100644 drivers/iio/light/apple-ib-als.c
create mode 100644 drivers/mfd/apple-ibridge.c
create mode 100644 include/linux/mfd/apple-ibridge.h
--
2.20.1
^ permalink raw reply
* [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
From: Ronald Tschalär @ 2019-04-22 3:12 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones
Cc: linux-input, linux-iio, linux-kernel
In-Reply-To: <20190422031251.11968-1-ronald@innovation.ch>
The iBridge device provides access to several devices, including:
- the Touch Bar
- the iSight webcam
- the light sensor
- the fingerprint sensor
This driver provides the core support for managing the iBridge device
and the access to the underlying devices. In particular, since the
functionality for the touch bar and light sensor is exposed via USB HID
interfaces, and the same HID device is used for multiple functions, this
driver provides a multiplexing layer that allows multiple HID drivers to
be registered for a given HID device. This allows the touch bar and ALS
driver to be separated out into their own modules.
Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
drivers/mfd/Kconfig | 15 +
drivers/mfd/Makefile | 1 +
drivers/mfd/apple-ibridge.c | 883 ++++++++++++++++++++++++++++++
include/linux/mfd/apple-ibridge.h | 39 ++
4 files changed, 938 insertions(+)
create mode 100644 drivers/mfd/apple-ibridge.c
create mode 100644 include/linux/mfd/apple-ibridge.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 76f9909cf396..d55fa77faacf 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1916,5 +1916,20 @@ config RAVE_SP_CORE
Select this to get support for the Supervisory Processor
device found on several devices in RAVE line of hardware.
+config MFD_APPLE_IBRIDGE
+ tristate "Apple iBridge chip"
+ depends on ACPI
+ depends on USB_HID
+ depends on X86 || COMPILE_TEST
+ select MFD_CORE
+ help
+ This MFD provides the core support for the Apple iBridge chip
+ found on recent MacBookPro's. The drivers for the Touch Bar
+ (apple-ib-tb) and light sensor (apple-ib-als) need to be
+ enabled separately.
+
+ To compile this driver as a module, choose M here: the
+ module will be called apple-ibridge.
+
endmenu
endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 12980a4ad460..c364e0e9d313 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -241,4 +241,5 @@ obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o
obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o
obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o
obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
+obj-$(CONFIG_MFD_APPLE_IBRIDGE) += apple-ibridge.o
diff --git a/drivers/mfd/apple-ibridge.c b/drivers/mfd/apple-ibridge.c
new file mode 100644
index 000000000000..56d325396961
--- /dev/null
+++ b/drivers/mfd/apple-ibridge.c
@@ -0,0 +1,883 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple iBridge Driver
+ *
+ * Copyright (c) 2018 Ronald Tschalär
+ */
+
+/**
+ * MacBookPro models with a Touch Bar (13,[23] and 14,[23]) have an Apple
+ * iBridge chip (also known as T1 chip) which exposes the touch bar,
+ * built-in webcam (iSight), ambient light sensor, and Secure Enclave
+ * Processor (SEP) for TouchID. It shows up in the system as a USB device
+ * with 3 configurations: 'Default iBridge Interfaces', 'Default iBridge
+ * Interfaces(OS X)', and 'Default iBridge Interfaces(Recovery)'. While
+ * the second one is used by MacOS to provide the fancy touch bar
+ * functionality with custom buttons etc, this driver just uses the first.
+ *
+ * In the first (default after boot) configuration, 4 usb interfaces are
+ * exposed: 2 related to the webcam, and 2 USB HID interfaces representing
+ * the touch bar and the ambient light sensor (and possibly the SEP,
+ * though at this point in time nothing is known about that). The webcam
+ * interfaces are already handled by the uvcvideo driver; furthermore, the
+ * handling of the input reports when "keys" on the touch bar are pressed
+ * is already handled properly by the generic USB HID core. This leaves
+ * the management of the touch bar modes (e.g. switching between function
+ * and special keys when the FN key is pressed), the touch bar display
+ * (dimming and turning off), the key-remapping when the FN key is
+ * pressed, and handling of the light sensor.
+ *
+ * This driver is implemented as an MFD driver, with the touch bar and ALS
+ * functions implemented by appropriate subdrivers (mfd cells). Because
+ * both those are basically hid drivers, but the current kernel driver
+ * structure does not allow more than one driver per device, this driver
+ * implements a demuxer for hid drivers: it registers itself as a hid
+ * driver with the core, and in turn it lets the subdrivers register
+ * themselves as hid drivers with this driver; the callbacks from the core
+ * are then forwarded to the subdrivers.
+ *
+ * Lastly, this driver also takes care of the power-management for the
+ * iBridge when suspending and resuming.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/list.h>
+#include <linux/mfd/apple-ibridge.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/rculist.h>
+#include <linux/slab.h>
+#include <linux/srcu.h>
+#include <linux/usb.h>
+
+#include "../hid/usbhid/usbhid.h"
+
+#define USB_ID_VENDOR_APPLE 0x05ac
+#define USB_ID_PRODUCT_IBRIDGE 0x8600
+
+#define APPLETB_BASIC_CONFIG 1
+
+#define LOG_DEV(ib_dev) (&(ib_dev)->acpi_dev->dev)
+
+struct appleib_device {
+ struct acpi_device *acpi_dev;
+ acpi_handle asoc_socw;
+ struct list_head hid_drivers;
+ struct list_head hid_devices;
+ struct mfd_cell *subdevs;
+ struct mutex update_lock; /* protect updates to all lists */
+ struct srcu_struct lists_srcu;
+ bool in_hid_probe;
+};
+
+struct appleib_hid_drv_info {
+ struct list_head entry;
+ struct hid_driver *driver;
+ void *driver_data;
+};
+
+struct appleib_hid_dev_info {
+ struct list_head entry;
+ struct list_head drivers;
+ struct hid_device *device;
+ const struct hid_device_id *device_id;
+ bool started;
+};
+
+static const struct mfd_cell appleib_subdevs[] = {
+ { .name = PLAT_NAME_IB_TB },
+ { .name = PLAT_NAME_IB_ALS },
+};
+
+static struct appleib_device *appleib_dev;
+
+#define call_void_driver_func(drv_info, fn, ...) \
+ do { \
+ if ((drv_info)->driver->fn) \
+ (drv_info)->driver->fn(__VA_ARGS__); \
+ } while (0)
+
+#define call_driver_func(drv_info, fn, ret_type, ...) \
+ ({ \
+ ret_type rc = 0; \
+ \
+ if ((drv_info)->driver->fn) \
+ rc = (drv_info)->driver->fn(__VA_ARGS__); \
+ \
+ rc; \
+ })
+
+static void appleib_remove_driver(struct appleib_device *ib_dev,
+ struct appleib_hid_drv_info *drv_info,
+ struct appleib_hid_dev_info *dev_info)
+{
+ list_del_rcu(&drv_info->entry);
+ synchronize_srcu(&ib_dev->lists_srcu);
+
+ call_void_driver_func(drv_info, remove, dev_info->device);
+
+ kfree(drv_info);
+}
+
+static int appleib_probe_driver(struct appleib_hid_drv_info *drv_info,
+ struct appleib_hid_dev_info *dev_info)
+{
+ struct appleib_hid_drv_info *d;
+ int rc = 0;
+
+ rc = call_driver_func(drv_info, probe, int, dev_info->device,
+ dev_info->device_id);
+ if (rc)
+ return rc;
+
+ d = kmemdup(drv_info, sizeof(*drv_info), GFP_KERNEL);
+ if (!d) {
+ call_void_driver_func(drv_info, remove, dev_info->device);
+ return -ENOMEM;
+ }
+
+ list_add_tail_rcu(&d->entry, &dev_info->drivers);
+ return 0;
+}
+
+static void appleib_remove_driver_attachments(struct appleib_device *ib_dev,
+ struct appleib_hid_dev_info *dev_info,
+ struct hid_driver *driver)
+{
+ struct appleib_hid_drv_info *drv_info;
+ struct appleib_hid_drv_info *tmp;
+
+ list_for_each_entry_safe(drv_info, tmp, &dev_info->drivers, entry) {
+ if (!driver || drv_info->driver == driver)
+ appleib_remove_driver(ib_dev, drv_info, dev_info);
+ }
+}
+
+/*
+ * Find all devices that are attached to this driver and detach them.
+ *
+ * Note: this must be run with update_lock held.
+ */
+static void appleib_detach_devices(struct appleib_device *ib_dev,
+ struct hid_driver *driver)
+{
+ struct appleib_hid_dev_info *dev_info;
+
+ list_for_each_entry(dev_info, &ib_dev->hid_devices, entry)
+ appleib_remove_driver_attachments(ib_dev, dev_info, driver);
+}
+
+/*
+ * Find all drivers that are attached to this device and detach them.
+ *
+ * Note: this must be run with update_lock held.
+ */
+static void appleib_detach_drivers(struct appleib_device *ib_dev,
+ struct appleib_hid_dev_info *dev_info)
+{
+ appleib_remove_driver_attachments(ib_dev, dev_info, NULL);
+}
+
+/**
+ * Unregister a previously registered HID driver from us.
+ * @ib_dev: the appleib_device from which to unregister the driver
+ * @driver: the driver to unregister
+ */
+int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
+ struct hid_driver *driver)
+{
+ struct appleib_hid_drv_info *drv_info;
+
+ mutex_lock(&ib_dev->update_lock);
+
+ list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
+ if (drv_info->driver == driver) {
+ appleib_detach_devices(ib_dev, driver);
+ list_del_rcu(&drv_info->entry);
+ mutex_unlock(&ib_dev->update_lock);
+ synchronize_srcu(&ib_dev->lists_srcu);
+ kfree(drv_info);
+ dev_info(LOG_DEV(ib_dev), "unregistered driver '%s'\n",
+ driver->name);
+ return 0;
+ }
+ }
+
+ mutex_unlock(&ib_dev->update_lock);
+
+ return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(appleib_unregister_hid_driver);
+
+static int appleib_start_hid_events(struct appleib_hid_dev_info *dev_info)
+{
+ struct hid_device *hdev = dev_info->device;
+ int rc;
+
+ rc = hid_connect(hdev, HID_CONNECT_DEFAULT);
+ if (rc) {
+ hid_err(hdev, "ib: hid connect failed (%d)\n", rc);
+ return rc;
+ }
+
+ rc = hid_hw_open(hdev);
+ if (rc) {
+ hid_err(hdev, "ib: failed to open hid: %d\n", rc);
+ hid_disconnect(hdev);
+ }
+
+ if (!rc)
+ dev_info->started = true;
+
+ return rc;
+}
+
+static void appleib_stop_hid_events(struct appleib_hid_dev_info *dev_info)
+{
+ if (dev_info->started) {
+ hid_hw_close(dev_info->device);
+ hid_disconnect(dev_info->device);
+ dev_info->started = false;
+ }
+}
+
+/**
+ * Register a HID driver with us.
+ * @ib_dev: the appleib_device with which to register the driver
+ * @driver: the driver to register
+ * @data: the driver-data to associate with the driver; this is available
+ * from appleib_get_drvdata(...).
+ */
+int appleib_register_hid_driver(struct appleib_device *ib_dev,
+ struct hid_driver *driver, void *data)
+{
+ struct appleib_hid_drv_info *drv_info;
+ struct appleib_hid_dev_info *dev_info;
+ int rc;
+
+ if (!driver->probe)
+ return -EINVAL;
+
+ drv_info = kzalloc(sizeof(*drv_info), GFP_KERNEL);
+ if (!drv_info)
+ return -ENOMEM;
+
+ drv_info->driver = driver;
+ drv_info->driver_data = data;
+
+ mutex_lock(&ib_dev->update_lock);
+
+ list_add_tail_rcu(&drv_info->entry, &ib_dev->hid_drivers);
+
+ list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
+ appleib_stop_hid_events(dev_info);
+
+ appleib_probe_driver(drv_info, dev_info);
+
+ rc = appleib_start_hid_events(dev_info);
+ if (rc)
+ appleib_detach_drivers(ib_dev, dev_info);
+ }
+
+ mutex_unlock(&ib_dev->update_lock);
+
+ dev_info(LOG_DEV(ib_dev), "registered driver '%s'\n", driver->name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(appleib_register_hid_driver);
+
+/**
+ * Get the driver-specific data associated with the given, previously
+ * registered HID driver (provided in the appleib_register_hid_driver()
+ * call).
+ * @ib_dev: the appleib_device with which the driver was registered
+ * @driver: the driver for which to get the data
+ */
+void *appleib_get_drvdata(struct appleib_device *ib_dev,
+ struct hid_driver *driver)
+{
+ struct appleib_hid_drv_info *drv_info;
+ void *drv_data = NULL;
+ int idx;
+
+ idx = srcu_read_lock(&ib_dev->lists_srcu);
+
+ list_for_each_entry_rcu(drv_info, &ib_dev->hid_drivers, entry) {
+ if (drv_info->driver == driver) {
+ drv_data = drv_info->driver_data;
+ break;
+ }
+ }
+
+ srcu_read_unlock(&ib_dev->lists_srcu, idx);
+
+ return drv_data;
+}
+EXPORT_SYMBOL_GPL(appleib_get_drvdata);
+
+/*
+ * Forward a hid-driver callback to all registered sub-drivers. This is for
+ * callbacks that return a status as an int.
+ * @hdev the hid-device
+ * @forward a function that calls the callback on the given driver
+ * @args arguments for the forward function
+ */
+static int appleib_forward_int_op(struct hid_device *hdev,
+ int (*forward)(struct appleib_hid_drv_info *,
+ struct hid_device *, void *),
+ void *args)
+{
+ struct appleib_device *ib_dev = hid_get_drvdata(hdev);
+ struct appleib_hid_dev_info *dev_info;
+ struct appleib_hid_drv_info *drv_info;
+ int idx;
+ int rc = 0;
+
+ idx = srcu_read_lock(&ib_dev->lists_srcu);
+
+ list_for_each_entry_rcu(dev_info, &ib_dev->hid_devices, entry) {
+ if (dev_info->device != hdev)
+ continue;
+
+ list_for_each_entry_rcu(drv_info, &dev_info->drivers, entry) {
+ rc = forward(drv_info, hdev, args);
+ if (rc)
+ break;
+ }
+
+ break;
+ }
+
+ srcu_read_unlock(&ib_dev->lists_srcu, idx);
+
+ return rc;
+}
+
+struct appleib_hid_event_args {
+ struct hid_field *field;
+ struct hid_usage *usage;
+ __s32 value;
+};
+
+static int appleib_hid_event_fwd(struct appleib_hid_drv_info *drv_info,
+ struct hid_device *hdev, void *args)
+{
+ struct appleib_hid_event_args *evt_args = args;
+
+ return call_driver_func(drv_info, event, int, hdev, evt_args->field,
+ evt_args->usage, evt_args->value);
+}
+
+static int appleib_hid_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ struct appleib_hid_event_args args = {
+ .field = field,
+ .usage = usage,
+ .value = value,
+ };
+
+ return appleib_forward_int_op(hdev, appleib_hid_event_fwd, &args);
+}
+
+static __u8 *appleib_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+ unsigned int *rsize)
+{
+ /* Some fields have a size of 64 bits, which according to HID 1.11
+ * Section 8.4 is not valid ("An item field cannot span more than 4
+ * bytes in a report"). Furthermore, hid_field_extract() complains
+ * when encountering such a field. So turn them into two 32-bit fields
+ * instead.
+ */
+
+ if (*rsize == 634 &&
+ /* Usage Page 0xff12 (vendor defined) */
+ rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
+ /* Usage 0x51 */
+ rdesc[416] == 0x09 && rdesc[417] == 0x51 &&
+ /* report size 64 */
+ rdesc[432] == 0x75 && rdesc[433] == 64 &&
+ /* report count 1 */
+ rdesc[434] == 0x95 && rdesc[435] == 1) {
+ rdesc[433] = 32;
+ rdesc[435] = 2;
+ hid_dbg(hdev, "Fixed up first 64-bit field\n");
+ }
+
+ if (*rsize == 634 &&
+ /* Usage Page 0xff12 (vendor defined) */
+ rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
+ /* Usage 0x51 */
+ rdesc[611] == 0x09 && rdesc[612] == 0x51 &&
+ /* report size 64 */
+ rdesc[627] == 0x75 && rdesc[628] == 64 &&
+ /* report count 1 */
+ rdesc[629] == 0x95 && rdesc[630] == 1) {
+ rdesc[628] = 32;
+ rdesc[630] = 2;
+ hid_dbg(hdev, "Fixed up second 64-bit field\n");
+ }
+
+ return rdesc;
+}
+
+static int appleib_input_configured_fwd(struct appleib_hid_drv_info *drv_info,
+ struct hid_device *hdev, void *args)
+{
+ return call_driver_func(drv_info, input_configured, int, hdev,
+ (struct hid_input *)args);
+}
+
+static int appleib_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
+{
+ return appleib_forward_int_op(hdev, appleib_input_configured_fwd,
+ hidinput);
+}
+
+#ifdef CONFIG_PM
+static int appleib_hid_suspend_fwd(struct appleib_hid_drv_info *drv_info,
+ struct hid_device *hdev, void *args)
+{
+ return call_driver_func(drv_info, suspend, int, hdev,
+ *(pm_message_t *)args);
+}
+
+static int appleib_hid_suspend(struct hid_device *hdev, pm_message_t message)
+{
+ return appleib_forward_int_op(hdev, appleib_hid_suspend_fwd, &message);
+}
+
+static int appleib_hid_resume_fwd(struct appleib_hid_drv_info *drv_info,
+ struct hid_device *hdev, void *args)
+{
+ return call_driver_func(drv_info, resume, int, hdev);
+}
+
+static int appleib_hid_resume(struct hid_device *hdev)
+{
+ return appleib_forward_int_op(hdev, appleib_hid_resume_fwd, NULL);
+}
+
+static int appleib_hid_reset_resume_fwd(struct appleib_hid_drv_info *drv_info,
+ struct hid_device *hdev, void *args)
+{
+ return call_driver_func(drv_info, reset_resume, int, hdev);
+}
+
+static int appleib_hid_reset_resume(struct hid_device *hdev)
+{
+ return appleib_forward_int_op(hdev, appleib_hid_reset_resume_fwd, NULL);
+}
+#endif /* CONFIG_PM */
+
+/**
+ * Find the field in the report with the given usage.
+ * @report: the report to search
+ * @field_usage: the usage of the field to search for
+ */
+struct hid_field *appleib_find_report_field(struct hid_report *report,
+ unsigned int field_usage)
+{
+ int f, u;
+
+ for (f = 0; f < report->maxfield; f++) {
+ struct hid_field *field = report->field[f];
+
+ if (field->logical == field_usage)
+ return field;
+
+ for (u = 0; u < field->maxusage; u++) {
+ if (field->usage[u].hid == field_usage)
+ return field;
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(appleib_find_report_field);
+
+/**
+ * Search all the reports of the device for the field with the given usage.
+ * @hdev: the device whose reports to search
+ * @application: the usage of application collection that the field must
+ * belong to
+ * @field_usage: the usage of the field to search for
+ */
+struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
+ unsigned int application,
+ unsigned int field_usage)
+{
+ static const int report_types[] = { HID_INPUT_REPORT, HID_OUTPUT_REPORT,
+ HID_FEATURE_REPORT };
+ struct hid_report *report;
+ struct hid_field *field;
+ int t;
+
+ for (t = 0; t < ARRAY_SIZE(report_types); t++) {
+ struct list_head *report_list =
+ &hdev->report_enum[report_types[t]].report_list;
+ list_for_each_entry(report, report_list, list) {
+ if (report->application != application)
+ continue;
+
+ field = appleib_find_report_field(report, field_usage);
+ if (field)
+ return field;
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(appleib_find_hid_field);
+
+/**
+ * Return whether we're currently inside a hid_device_probe or not.
+ * @ib_dev: the appleib_device
+ */
+bool appleib_in_hid_probe(struct appleib_device *ib_dev)
+{
+ return ib_dev->in_hid_probe;
+}
+EXPORT_SYMBOL_GPL(appleib_in_hid_probe);
+
+static struct appleib_hid_dev_info *
+appleib_add_device(struct appleib_device *ib_dev, struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ struct appleib_hid_dev_info *dev_info;
+ struct appleib_hid_drv_info *drv_info;
+
+ /* allocate device-info for this device */
+ dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
+ if (!dev_info)
+ return NULL;
+
+ INIT_LIST_HEAD(&dev_info->drivers);
+ dev_info->device = hdev;
+ dev_info->device_id = id;
+
+ /* notify all our sub drivers */
+ mutex_lock(&ib_dev->update_lock);
+
+ ib_dev->in_hid_probe = true;
+
+ list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
+ appleib_probe_driver(drv_info, dev_info);
+ }
+
+ ib_dev->in_hid_probe = false;
+
+ /* remember this new device */
+ list_add_tail_rcu(&dev_info->entry, &ib_dev->hid_devices);
+
+ mutex_unlock(&ib_dev->update_lock);
+
+ return dev_info;
+}
+
+static void appleib_remove_device(struct appleib_device *ib_dev,
+ struct appleib_hid_dev_info *dev_info)
+{
+ list_del_rcu(&dev_info->entry);
+ synchronize_srcu(&ib_dev->lists_srcu);
+
+ appleib_detach_drivers(ib_dev, dev_info);
+
+ kfree(dev_info);
+}
+
+static int appleib_hid_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ struct appleib_device *ib_dev;
+ struct appleib_hid_dev_info *dev_info;
+ struct usb_device *udev;
+ int rc;
+
+ /* check usb config first */
+ udev = hid_to_usb_dev(hdev);
+
+ if (udev->actconfig->desc.bConfigurationValue != APPLETB_BASIC_CONFIG) {
+ rc = usb_driver_set_configuration(udev, APPLETB_BASIC_CONFIG);
+ return rc ? rc : -ENODEV;
+ }
+
+ /* Assign the driver data */
+ ib_dev = appleib_dev;
+ hid_set_drvdata(hdev, ib_dev);
+
+ /* initialize the report info */
+ rc = hid_parse(hdev);
+ if (rc) {
+ hid_err(hdev, "ib: hid parse failed (%d)\n", rc);
+ goto error;
+ }
+
+ /* alloc bufs etc so probe's can send requests; but connect later */
+ rc = hid_hw_start(hdev, 0);
+ if (rc) {
+ hid_err(hdev, "ib: hw start failed (%d)\n", rc);
+ goto error;
+ }
+
+ /* add this hdev to our device list */
+ dev_info = appleib_add_device(ib_dev, hdev, id);
+ if (!dev_info) {
+ rc = -ENOMEM;
+ goto stop_hw;
+ }
+
+ /* start the hid */
+ rc = appleib_start_hid_events(dev_info);
+ if (rc)
+ goto remove_dev;
+
+ return 0;
+
+remove_dev:
+ mutex_lock(&ib_dev->update_lock);
+ appleib_remove_device(ib_dev, dev_info);
+ mutex_unlock(&ib_dev->update_lock);
+stop_hw:
+ hid_hw_stop(hdev);
+error:
+ return rc;
+}
+
+static void appleib_hid_remove(struct hid_device *hdev)
+{
+ struct appleib_device *ib_dev = hid_get_drvdata(hdev);
+ struct appleib_hid_dev_info *dev_info;
+
+ mutex_lock(&ib_dev->update_lock);
+
+ list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
+ if (dev_info->device == hdev) {
+ appleib_stop_hid_events(dev_info);
+ appleib_remove_device(ib_dev, dev_info);
+ break;
+ }
+ }
+
+ mutex_unlock(&ib_dev->update_lock);
+
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id appleib_hid_devices[] = {
+ { HID_USB_DEVICE(USB_ID_VENDOR_APPLE, USB_ID_PRODUCT_IBRIDGE) },
+ { },
+};
+
+static struct hid_driver appleib_hid_driver = {
+ .name = "apple-ibridge-hid",
+ .id_table = appleib_hid_devices,
+ .probe = appleib_hid_probe,
+ .remove = appleib_hid_remove,
+ .event = appleib_hid_event,
+ .report_fixup = appleib_report_fixup,
+ .input_configured = appleib_input_configured,
+#ifdef CONFIG_PM
+ .suspend = appleib_hid_suspend,
+ .resume = appleib_hid_resume,
+ .reset_resume = appleib_hid_reset_resume,
+#endif
+};
+
+static struct appleib_device *appleib_alloc_device(struct acpi_device *acpi_dev)
+{
+ struct appleib_device *ib_dev;
+ acpi_status sts;
+ int rc;
+
+ /* allocate */
+ ib_dev = kzalloc(sizeof(*ib_dev), GFP_KERNEL);
+ if (!ib_dev)
+ return ERR_PTR(-ENOMEM);
+
+ /* init structures */
+ INIT_LIST_HEAD(&ib_dev->hid_drivers);
+ INIT_LIST_HEAD(&ib_dev->hid_devices);
+ mutex_init(&ib_dev->update_lock);
+ init_srcu_struct(&ib_dev->lists_srcu);
+
+ ib_dev->acpi_dev = acpi_dev;
+
+ /* get iBridge acpi power control method */
+ sts = acpi_get_handle(acpi_dev->handle, "SOCW", &ib_dev->asoc_socw);
+ if (ACPI_FAILURE(sts)) {
+ dev_err(LOG_DEV(ib_dev),
+ "Error getting handle for ASOC.SOCW method: %s\n",
+ acpi_format_exception(sts));
+ rc = -ENXIO;
+ goto free_mem;
+ }
+
+ /* ensure iBridge is powered on */
+ sts = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
+ if (ACPI_FAILURE(sts))
+ dev_warn(LOG_DEV(ib_dev), "SOCW(1) failed: %s\n",
+ acpi_format_exception(sts));
+
+ return ib_dev;
+
+free_mem:
+ kfree(ib_dev);
+ return ERR_PTR(rc);
+}
+
+static int appleib_probe(struct acpi_device *acpi)
+{
+ struct appleib_device *ib_dev;
+ struct appleib_platform_data *pdata;
+ int i;
+ int ret;
+
+ if (appleib_dev)
+ return -EBUSY;
+
+ ib_dev = appleib_alloc_device(acpi);
+ if (IS_ERR_OR_NULL(ib_dev))
+ return PTR_ERR(ib_dev);
+
+ ib_dev->subdevs = kmemdup(appleib_subdevs, sizeof(appleib_subdevs),
+ GFP_KERNEL);
+ if (!ib_dev->subdevs) {
+ ret = -ENOMEM;
+ goto free_dev;
+ }
+
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ ret = -ENOMEM;
+ goto free_subdevs;
+ }
+
+ pdata->ib_dev = ib_dev;
+ pdata->log_dev = LOG_DEV(ib_dev);
+ for (i = 0; i < ARRAY_SIZE(appleib_subdevs); i++) {
+ ib_dev->subdevs[i].platform_data = pdata;
+ ib_dev->subdevs[i].pdata_size = sizeof(*pdata);
+ }
+
+ ret = mfd_add_devices(&acpi->dev, PLATFORM_DEVID_NONE,
+ ib_dev->subdevs, ARRAY_SIZE(appleib_subdevs),
+ NULL, 0, NULL);
+ if (ret) {
+ dev_err(LOG_DEV(ib_dev), "Error adding MFD devices: %d\n", ret);
+ goto free_pdata;
+ }
+
+ acpi->driver_data = ib_dev;
+ appleib_dev = ib_dev;
+
+ ret = hid_register_driver(&appleib_hid_driver);
+ if (ret) {
+ dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
+ ret);
+ goto rem_mfd_devs;
+ }
+
+ return 0;
+
+rem_mfd_devs:
+ mfd_remove_devices(&acpi->dev);
+free_pdata:
+ kfree(pdata);
+free_subdevs:
+ kfree(ib_dev->subdevs);
+free_dev:
+ appleib_dev = NULL;
+ acpi->driver_data = NULL;
+ kfree(ib_dev);
+ return ret;
+}
+
+static int appleib_remove(struct acpi_device *acpi)
+{
+ struct appleib_device *ib_dev = acpi_driver_data(acpi);
+
+ mfd_remove_devices(&acpi->dev);
+ hid_unregister_driver(&appleib_hid_driver);
+
+ if (appleib_dev == ib_dev)
+ appleib_dev = NULL;
+
+ kfree(ib_dev->subdevs[0].platform_data);
+ kfree(ib_dev->subdevs);
+ kfree(ib_dev);
+
+ return 0;
+}
+
+static int appleib_suspend(struct device *dev)
+{
+ struct acpi_device *adev;
+ struct appleib_device *ib_dev;
+ int rc;
+
+ adev = to_acpi_device(dev);
+ ib_dev = acpi_driver_data(adev);
+
+ rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
+ if (ACPI_FAILURE(rc))
+ dev_warn(LOG_DEV(ib_dev), "SOCW(0) failed: %s\n",
+ acpi_format_exception(rc));
+
+ return 0;
+}
+
+static int appleib_resume(struct device *dev)
+{
+ struct acpi_device *adev;
+ struct appleib_device *ib_dev;
+ int rc;
+
+ adev = to_acpi_device(dev);
+ ib_dev = acpi_driver_data(adev);
+
+ rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
+ if (ACPI_FAILURE(rc))
+ dev_warn(LOG_DEV(ib_dev), "SOCW(1) failed: %s\n",
+ acpi_format_exception(rc));
+
+ return 0;
+}
+
+static const struct dev_pm_ops appleib_pm = {
+ .suspend = appleib_suspend,
+ .resume = appleib_resume,
+ .restore = appleib_resume,
+};
+
+static const struct acpi_device_id appleib_acpi_match[] = {
+ { "APP7777", 0 },
+ { },
+};
+
+MODULE_DEVICE_TABLE(acpi, appleib_acpi_match);
+
+static struct acpi_driver appleib_driver = {
+ .name = "apple-ibridge",
+ .class = "topcase", /* ? */
+ .owner = THIS_MODULE,
+ .ids = appleib_acpi_match,
+ .ops = {
+ .add = appleib_probe,
+ .remove = appleib_remove,
+ },
+ .drv = {
+ .pm = &appleib_pm,
+ },
+};
+
+module_acpi_driver(appleib_driver)
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("Apple iBridge driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/apple-ibridge.h b/include/linux/mfd/apple-ibridge.h
new file mode 100644
index 000000000000..d321714767f7
--- /dev/null
+++ b/include/linux/mfd/apple-ibridge.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Apple iBridge Driver
+ *
+ * Copyright (c) 2018 Ronald Tschalär
+ */
+
+#ifndef __LINUX_MFD_APPLE_IBRDIGE_H
+#define __LINUX_MFD_APPLE_IBRDIGE_H
+
+#include <linux/device.h>
+#include <linux/hid.h>
+
+#define PLAT_NAME_IB_TB "apple-ib-tb"
+#define PLAT_NAME_IB_ALS "apple-ib-als"
+
+struct appleib_device;
+
+struct appleib_platform_data {
+ struct appleib_device *ib_dev;
+ struct device *log_dev;
+};
+
+int appleib_register_hid_driver(struct appleib_device *ib_dev,
+ struct hid_driver *driver, void *data);
+int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
+ struct hid_driver *driver);
+
+void *appleib_get_drvdata(struct appleib_device *ib_dev,
+ struct hid_driver *driver);
+bool appleib_in_hid_probe(struct appleib_device *ib_dev);
+
+struct hid_field *appleib_find_report_field(struct hid_report *report,
+ unsigned int field_usage);
+struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
+ unsigned int application,
+ unsigned int field_usage);
+
+#endif
--
2.20.1
^ permalink raw reply related
* [PATCH 2/3] HID: apple-ib-tb: Add driver for the Touch Bar on MacBook Pro's.
From: Ronald Tschalär @ 2019-04-22 3:12 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones
Cc: linux-input, linux-iio, linux-kernel
In-Reply-To: <20190422031251.11968-1-ronald@innovation.ch>
This driver enables basic touch bar functionality: enabling it, switching
between modes on FN key press, and dimming and turning the display
off/on when idle/active.
Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
drivers/hid/Kconfig | 10 +
drivers/hid/Makefile | 1 +
drivers/hid/apple-ib-tb.c | 1288 +++++++++++++++++++++++++++++++++++++
3 files changed, 1299 insertions(+)
create mode 100644 drivers/hid/apple-ib-tb.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 41e9935fc584..f0a65bb4be6e 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -135,6 +135,16 @@ config HID_APPLE
Say Y here if you want support for keyboards of Apple iBooks, PowerBooks,
MacBooks, MacBook Pros and Apple Aluminum.
+config HID_APPLE_IBRIDGE_TB
+ tristate "Apple iBridge Touch Bar"
+ depends on MFD_APPLE_IBRIDGE
+ ---help---
+ Say Y here if you want support for the Touch Bar on recent
+ MacBook Pros.
+
+ To compile this driver as a module, choose M here: the
+ module will be called apple-ib-tb.
+
config HID_APPLEIR
tristate "Apple infrared receiver"
depends on (USB_HID)
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 896a51ce7ce0..dedd8049d3fb 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_HID_ACCUTOUCH) += hid-accutouch.o
obj-$(CONFIG_HID_ALPS) += hid-alps.o
obj-$(CONFIG_HID_ACRUX) += hid-axff.o
obj-$(CONFIG_HID_APPLE) += hid-apple.o
+obj-$(CONFIG_HID_APPLE_IBRIDGE_TB) += apple-ib-tb.o
obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o
obj-$(CONFIG_HID_ASUS) += hid-asus.o
obj-$(CONFIG_HID_AUREAL) += hid-aureal.o
diff --git a/drivers/hid/apple-ib-tb.c b/drivers/hid/apple-ib-tb.c
new file mode 100644
index 000000000000..6b72ff56b17f
--- /dev/null
+++ b/drivers/hid/apple-ib-tb.c
@@ -0,0 +1,1288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Touch Bar Driver
+ *
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ */
+
+/*
+ * Recent MacBookPro models (13,[23] and 14,[23]) have a touch bar, which
+ * is exposed via several USB interfaces. MacOS supports a fancy mode
+ * where arbitrary buttons can be defined; this driver currently only
+ * supports the simple mode that consists of 3 predefined layouts
+ * (escape-only, esc + special keys, and esc + function keys).
+ *
+ * The first USB HID interface supports two reports, an input report that
+ * is used to report the key presses, and an output report which can be
+ * used to set the touch bar "mode": touch bar off (in which case no touches
+ * are reported at all), escape key only, escape + 12 function keys, and
+ * escape + several special keys (including brightness, audio volume,
+ * etc). The second interface supports several, complex reports, most of
+ * which are unknown at this time, but one of which has been determined to
+ * allow for controlling of the touch bar's brightness: off (though touches
+ * are still reported), dimmed, and full brightness. This driver makes
+ * use of these two reports.
+ */
+
+#define dev_fmt(fmt) "tb: " fmt
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/jiffies.h>
+#include <linux/ktime.h>
+#include <linux/mfd/apple-ibridge.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#define HID_UP_APPLE 0xff120000
+#define HID_USAGE_MODE (HID_UP_CUSTOM | 0x0004)
+#define HID_USAGE_APPLE_APP (HID_UP_APPLE | 0x0001)
+#define HID_USAGE_DISP (HID_UP_APPLE | 0x0021)
+
+#define APPLETB_MAX_TB_KEYS 13 /* ESC, F1-F12 */
+
+#define APPLETB_CMD_MODE_ESC 0
+#define APPLETB_CMD_MODE_FN 1
+#define APPLETB_CMD_MODE_SPCL 2
+#define APPLETB_CMD_MODE_OFF 3
+#define APPLETB_CMD_MODE_NONE 255
+
+#define APPLETB_CMD_DISP_ON 1
+#define APPLETB_CMD_DISP_DIM 2
+#define APPLETB_CMD_DISP_OFF 4
+#define APPLETB_CMD_DISP_NONE 255
+
+#define APPLETB_FN_MODE_FKEYS 0
+#define APPLETB_FN_MODE_NORM 1
+#define APPLETB_FN_MODE_INV 2
+#define APPLETB_FN_MODE_SPCL 3
+#define APPLETB_FN_MODE_MAX APPLETB_FN_MODE_SPCL
+
+#define APPLETB_DEVID_KEYBOARD 1
+#define APPLETB_DEVID_TOUCHPAD 2
+
+#define APPLETB_MAX_DIM_TIME 30
+
+static int appletb_tb_def_idle_timeout = 5 * 60;
+module_param_named(idle_timeout, appletb_tb_def_idle_timeout, int, 0444);
+MODULE_PARM_DESC(idle_timeout, "Default touch bar idle timeout (in seconds); 0 disables touch bar, -1 disables timeout");
+
+static int appletb_tb_def_dim_timeout = -2;
+module_param_named(dim_timeout, appletb_tb_def_dim_timeout, int, 0444);
+MODULE_PARM_DESC(dim_timeout, "Default touch bar dim timeout (in seconds); 0 means always dimmmed, -1 disables dimming, [-2] calculates timeout based on idle-timeout");
+
+static int appletb_tb_def_fn_mode = APPLETB_FN_MODE_NORM;
+module_param_named(fnmode, appletb_tb_def_fn_mode, int, 0444);
+MODULE_PARM_DESC(fnmode, "Default Fn key mode: 0 = f-keys only, [1] = fn key switches from special to f-keys, 2 = inverse of 1, 3 = special keys only");
+
+static ssize_t idle_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t idle_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size);
+static DEVICE_ATTR_RW(idle_timeout);
+
+static ssize_t dim_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t dim_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size);
+static DEVICE_ATTR_RW(dim_timeout);
+
+static ssize_t fnmode_show(struct device *dev, struct device_attribute *attr,
+ char *buf);
+static ssize_t fnmode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size);
+static DEVICE_ATTR_RW(fnmode);
+
+static struct attribute *appletb_attrs[] = {
+ &dev_attr_idle_timeout.attr,
+ &dev_attr_dim_timeout.attr,
+ &dev_attr_fnmode.attr,
+ NULL,
+};
+
+static const struct attribute_group appletb_attr_group = {
+ .attrs = appletb_attrs,
+};
+
+struct appletb_device {
+ bool active;
+ struct device *log_dev;
+
+ struct appletb_report_info {
+ struct hid_device *hdev;
+ struct usb_interface *usb_iface;
+ unsigned int usb_epnum;
+ unsigned int report_id;
+ unsigned int report_type;
+ bool suspended;
+ } mode_info, disp_info;
+
+ struct input_handler inp_handler;
+ struct input_handle kbd_handle;
+ struct input_handle tpd_handle;
+
+ bool last_tb_keys_pressed[APPLETB_MAX_TB_KEYS];
+ bool last_tb_keys_translated[APPLETB_MAX_TB_KEYS];
+ bool last_fn_pressed;
+
+ ktime_t last_event_time;
+
+ unsigned char cur_tb_mode;
+ unsigned char pnd_tb_mode;
+ unsigned char cur_tb_disp;
+ unsigned char pnd_tb_disp;
+ bool tb_autopm_off;
+ bool restore_autopm;
+ struct delayed_work tb_work;
+ /* protects most of the above */
+ spinlock_t tb_lock;
+
+ int dim_timeout;
+ int idle_timeout;
+ bool dim_to_is_calc;
+ int fn_mode;
+};
+
+struct appletb_key_translation {
+ u16 from;
+ u16 to;
+};
+
+static const struct appletb_key_translation appletb_fn_codes[] = {
+ { KEY_F1, KEY_BRIGHTNESSDOWN },
+ { KEY_F2, KEY_BRIGHTNESSUP },
+ { KEY_F3, KEY_SCALE }, /* not used */
+ { KEY_F4, KEY_DASHBOARD }, /* not used */
+ { KEY_F5, KEY_KBDILLUMDOWN },
+ { KEY_F6, KEY_KBDILLUMUP },
+ { KEY_F7, KEY_PREVIOUSSONG },
+ { KEY_F8, KEY_PLAYPAUSE },
+ { KEY_F9, KEY_NEXTSONG },
+ { KEY_F10, KEY_MUTE },
+ { KEY_F11, KEY_VOLUMEDOWN },
+ { KEY_F12, KEY_VOLUMEUP },
+};
+
+static struct hid_driver appletb_hid_driver;
+
+static int appletb_send_hid_report(struct appletb_report_info *rinfo,
+ __u8 requesttype, void *data, __u16 size)
+{
+ void *buffer;
+ struct usb_device *dev = interface_to_usbdev(rinfo->usb_iface);
+ u8 ifnum = rinfo->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ int tries = 0;
+ int rc;
+
+ buffer = kmemdup(data, size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ do {
+ rc = usb_control_msg(dev,
+ usb_sndctrlpipe(dev, rinfo->usb_epnum),
+ HID_REQ_SET_REPORT, requesttype,
+ rinfo->report_type << 8 | rinfo->report_id,
+ ifnum, buffer, size, 2000);
+ if (rc != -EPIPE)
+ break;
+
+ usleep_range(1000 << tries, 3000 << tries);
+ } while (++tries < 5);
+
+ kfree(buffer);
+
+ return (rc > 0) ? 0 : rc;
+}
+
+static bool appletb_disable_autopm(struct appletb_report_info *rinfo)
+{
+ int rc;
+
+ rc = usb_autopm_get_interface(rinfo->usb_iface);
+ if (rc == 0)
+ return true;
+
+ hid_err(rinfo->hdev,
+ "Failed to disable auto-pm on touch bar device (%d)\n", rc);
+ return false;
+}
+
+static int appletb_set_tb_mode(struct appletb_device *tb_dev,
+ unsigned char mode)
+{
+ int rc;
+ bool autopm_off = false;
+
+ if (!tb_dev->mode_info.usb_iface)
+ return -ENOTCONN;
+
+ autopm_off = appletb_disable_autopm(&tb_dev->mode_info);
+
+ rc = appletb_send_hid_report(&tb_dev->mode_info,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ &mode, 1);
+ if (rc < 0)
+ dev_err(tb_dev->log_dev,
+ "Failed to set touch bar mode to %u (%d)\n", mode, rc);
+
+ if (autopm_off)
+ usb_autopm_put_interface(tb_dev->mode_info.usb_iface);
+
+ return rc;
+}
+
+static int appletb_set_tb_disp(struct appletb_device *tb_dev,
+ unsigned char disp)
+{
+ unsigned char report[] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ if (!tb_dev->disp_info.usb_iface)
+ return -ENOTCONN;
+
+ /*
+ * Keep the USB interface powered on while the touch bar display is on
+ * for better responsiveness.
+ */
+ if (disp != APPLETB_CMD_DISP_OFF &&
+ tb_dev->cur_tb_disp == APPLETB_CMD_DISP_OFF)
+ tb_dev->tb_autopm_off =
+ appletb_disable_autopm(&tb_dev->disp_info);
+
+ report[0] = tb_dev->disp_info.report_id;
+ report[2] = disp;
+
+ rc = appletb_send_hid_report(&tb_dev->disp_info,
+ USB_DIR_OUT | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE,
+ report, sizeof(report));
+ if (rc < 0)
+ dev_err(tb_dev->log_dev,
+ "Failed to set touch bar display to %u (%d)\n", disp,
+ rc);
+
+ if (disp == APPLETB_CMD_DISP_OFF &&
+ tb_dev->cur_tb_disp != APPLETB_CMD_DISP_OFF) {
+ if (tb_dev->tb_autopm_off) {
+ usb_autopm_put_interface(tb_dev->disp_info.usb_iface);
+ tb_dev->tb_autopm_off = false;
+ }
+ }
+
+ return rc;
+}
+
+static bool appletb_any_tb_key_pressed(struct appletb_device *tb_dev)
+{
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(tb_dev->last_tb_keys_pressed); idx++) {
+ if (tb_dev->last_tb_keys_pressed[idx])
+ return true;
+ }
+
+ return false;
+}
+
+static void appletb_schedule_tb_update(struct appletb_device *tb_dev, s64 secs)
+{
+ schedule_delayed_work(&tb_dev->tb_work, msecs_to_jiffies(secs * 1000));
+}
+
+static void appletb_set_tb_worker(struct work_struct *work)
+{
+ struct appletb_device *tb_dev =
+ container_of(work, struct appletb_device, tb_work.work);
+ s64 time_left, min_timeout, time_to_off;
+ unsigned char pending_mode;
+ unsigned char pending_disp;
+ unsigned char current_disp;
+ bool restore_autopm;
+ bool any_tb_key_pressed, need_reschedule;
+ int rc1 = 1, rc2 = 1;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ /* handle explicit mode-change request */
+ pending_mode = tb_dev->pnd_tb_mode;
+ pending_disp = tb_dev->pnd_tb_disp;
+ restore_autopm = tb_dev->restore_autopm;
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+ if (pending_mode != APPLETB_CMD_MODE_NONE)
+ rc1 = appletb_set_tb_mode(tb_dev, pending_mode);
+ if (pending_mode != APPLETB_CMD_MODE_NONE &&
+ pending_disp != APPLETB_CMD_DISP_NONE)
+ msleep(25);
+ if (pending_disp != APPLETB_CMD_DISP_NONE)
+ rc2 = appletb_set_tb_disp(tb_dev, pending_disp);
+
+ if (restore_autopm && tb_dev->tb_autopm_off)
+ appletb_disable_autopm(&tb_dev->disp_info);
+
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ need_reschedule = false;
+
+ if (rc1 == 0) {
+ tb_dev->cur_tb_mode = pending_mode;
+
+ if (tb_dev->pnd_tb_mode == pending_mode)
+ tb_dev->pnd_tb_mode = APPLETB_CMD_MODE_NONE;
+ else
+ need_reschedule = true;
+ }
+
+ if (rc2 == 0) {
+ tb_dev->cur_tb_disp = pending_disp;
+
+ if (tb_dev->pnd_tb_disp == pending_disp)
+ tb_dev->pnd_tb_disp = APPLETB_CMD_DISP_NONE;
+ else
+ need_reschedule = true;
+ }
+ current_disp = tb_dev->cur_tb_disp;
+
+ tb_dev->restore_autopm = false;
+
+ /* calculate time left to next timeout */
+ if (tb_dev->idle_timeout <= 0 && tb_dev->dim_timeout <= 0)
+ min_timeout = -1;
+ else if (tb_dev->dim_timeout <= 0)
+ min_timeout = tb_dev->idle_timeout;
+ else if (tb_dev->idle_timeout <= 0)
+ min_timeout = tb_dev->dim_timeout;
+ else
+ min_timeout = min(tb_dev->dim_timeout, tb_dev->idle_timeout);
+
+ if (min_timeout > 0) {
+ s64 idle_time =
+ (ktime_ms_delta(ktime_get(), tb_dev->last_event_time) +
+ 500) / 1000;
+
+ time_left = max(min_timeout - idle_time, 0LL);
+ if (tb_dev->idle_timeout <= 0)
+ time_to_off = -1;
+ else if (idle_time >= tb_dev->idle_timeout)
+ time_to_off = 0;
+ else
+ time_to_off = tb_dev->idle_timeout - idle_time;
+ }
+
+ any_tb_key_pressed = appletb_any_tb_key_pressed(tb_dev);
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+ /* a new command arrived while we were busy - handle it */
+ if (need_reschedule) {
+ appletb_schedule_tb_update(tb_dev, 0);
+ return;
+ }
+
+ /* if no idle/dim timeout, we're done */
+ if (min_timeout <= 0)
+ return;
+
+ /* manage idle/dim timeout */
+ if (time_left > 0) {
+ /* we fired too soon or had a mode-change - re-schedule */
+ appletb_schedule_tb_update(tb_dev, time_left);
+ } else if (any_tb_key_pressed) {
+ /* keys are still pressed - re-schedule */
+ appletb_schedule_tb_update(tb_dev, min_timeout);
+ } else {
+ /* dim or idle timeout reached */
+ int next_disp = (time_to_off == 0) ? APPLETB_CMD_DISP_OFF :
+ APPLETB_CMD_DISP_DIM;
+ if (next_disp != current_disp &&
+ appletb_set_tb_disp(tb_dev, next_disp) == 0) {
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+ tb_dev->cur_tb_disp = next_disp;
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+ }
+
+ if (time_to_off > 0)
+ appletb_schedule_tb_update(tb_dev, time_to_off);
+ }
+}
+
+static u16 appletb_fn_to_special(u16 code)
+{
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(appletb_fn_codes); idx++) {
+ if (appletb_fn_codes[idx].from == code)
+ return appletb_fn_codes[idx].to;
+ }
+
+ return 0;
+}
+
+static unsigned char appletb_get_cur_tb_mode(struct appletb_device *tb_dev)
+{
+ return tb_dev->pnd_tb_mode != APPLETB_CMD_MODE_NONE ?
+ tb_dev->pnd_tb_mode : tb_dev->cur_tb_mode;
+}
+
+static unsigned char appletb_get_cur_tb_disp(struct appletb_device *tb_dev)
+{
+ return tb_dev->pnd_tb_disp != APPLETB_CMD_DISP_NONE ?
+ tb_dev->pnd_tb_disp : tb_dev->cur_tb_disp;
+}
+
+static unsigned char appletb_get_fn_tb_mode(struct appletb_device *tb_dev)
+{
+ switch (tb_dev->fn_mode) {
+ case APPLETB_FN_MODE_FKEYS:
+ return APPLETB_CMD_MODE_FN;
+
+ case APPLETB_FN_MODE_SPCL:
+ return APPLETB_CMD_MODE_SPCL;
+
+ case APPLETB_FN_MODE_INV:
+ return (tb_dev->last_fn_pressed) ? APPLETB_CMD_MODE_SPCL :
+ APPLETB_CMD_MODE_FN;
+
+ case APPLETB_FN_MODE_NORM:
+ default:
+ return (tb_dev->last_fn_pressed) ? APPLETB_CMD_MODE_FN :
+ APPLETB_CMD_MODE_SPCL;
+ }
+}
+
+/*
+ * Switch touch bar mode and display when mode or display not the desired ones.
+ */
+static void appletb_update_touchbar_no_lock(struct appletb_device *tb_dev,
+ bool force)
+{
+ unsigned char want_mode;
+ unsigned char want_disp;
+ bool need_update = false;
+
+ /*
+ * Calculate the new modes:
+ * idle_timeout:
+ * -1 always on
+ * 0 always off
+ * >0 turn off after idle_timeout seconds
+ * dim_timeout (only valid if idle_timeout != 0):
+ * -1 never dimmed
+ * 0 always dimmed
+ * >0 dim off after dim_timeout seconds
+ */
+ if (tb_dev->idle_timeout == 0) {
+ want_mode = APPLETB_CMD_MODE_OFF;
+ want_disp = APPLETB_CMD_DISP_OFF;
+ } else {
+ want_mode = appletb_get_fn_tb_mode(tb_dev);
+ want_disp = tb_dev->dim_timeout == 0 ? APPLETB_CMD_DISP_DIM :
+ APPLETB_CMD_DISP_ON;
+ }
+
+ /*
+ * See if we need to update the touch bar, taking into account that we
+ * generally don't want to switch modes while a touch bar key is
+ * pressed.
+ */
+ if (appletb_get_cur_tb_mode(tb_dev) != want_mode &&
+ !appletb_any_tb_key_pressed(tb_dev)) {
+ tb_dev->pnd_tb_mode = want_mode;
+ need_update = true;
+ }
+
+ if (appletb_get_cur_tb_disp(tb_dev) != want_disp &&
+ (!appletb_any_tb_key_pressed(tb_dev) ||
+ (appletb_any_tb_key_pressed(tb_dev) &&
+ want_disp != APPLETB_CMD_DISP_OFF))) {
+ tb_dev->pnd_tb_disp = want_disp;
+ need_update = true;
+ }
+
+ if (force)
+ need_update = true;
+
+ /* schedule the update if desired */
+ dev_dbg_ratelimited(tb_dev->log_dev,
+ "update: need_update=%d, want_mode=%d, cur-mode=%d, want_disp=%d, cur-disp=%d\n",
+ need_update, want_mode, tb_dev->cur_tb_mode,
+ want_disp, tb_dev->cur_tb_disp);
+ if (need_update) {
+ cancel_delayed_work(&tb_dev->tb_work);
+ appletb_schedule_tb_update(tb_dev, 0);
+ }
+}
+
+static void appletb_update_touchbar(struct appletb_device *tb_dev, bool force)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ if (tb_dev->active)
+ appletb_update_touchbar_no_lock(tb_dev, force);
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+}
+
+static void appletb_set_idle_timeout(struct appletb_device *tb_dev, int new)
+{
+ tb_dev->idle_timeout = new;
+ if (tb_dev->dim_to_is_calc)
+ tb_dev->dim_timeout = new - min(APPLETB_MAX_DIM_TIME, new / 3);
+}
+
+static ssize_t idle_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", tb_dev->idle_timeout);
+}
+
+static ssize_t idle_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+ long new;
+ int rc;
+
+ rc = kstrtol(buf, 0, &new);
+ if (rc || new > INT_MAX || new < -1)
+ return -EINVAL;
+
+ appletb_set_idle_timeout(tb_dev, new);
+ appletb_update_touchbar(tb_dev, true);
+
+ return size;
+}
+
+static void appletb_set_dim_timeout(struct appletb_device *tb_dev, int new)
+{
+ if (new == -2) {
+ tb_dev->dim_to_is_calc = true;
+ appletb_set_idle_timeout(tb_dev, tb_dev->idle_timeout);
+ } else {
+ tb_dev->dim_to_is_calc = false;
+ tb_dev->dim_timeout = new;
+ }
+}
+
+static ssize_t dim_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ tb_dev->dim_to_is_calc ? -2 : tb_dev->dim_timeout);
+}
+
+static ssize_t dim_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+ long new;
+ int rc;
+
+ rc = kstrtol(buf, 0, &new);
+ if (rc || new > INT_MAX || new < -2)
+ return -EINVAL;
+
+ appletb_set_dim_timeout(tb_dev, new);
+ appletb_update_touchbar(tb_dev, true);
+
+ return size;
+}
+
+static ssize_t fnmode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", tb_dev->fn_mode);
+}
+
+static ssize_t fnmode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(dev_get_drvdata(dev), &appletb_hid_driver);
+ long new;
+ int rc;
+
+ rc = kstrtol(buf, 0, &new);
+ if (rc || new > APPLETB_FN_MODE_MAX || new < 0)
+ return -EINVAL;
+
+ tb_dev->fn_mode = new;
+ appletb_update_touchbar(tb_dev, false);
+
+ return size;
+}
+
+static int appletb_tb_key_to_slot(unsigned int code)
+{
+ switch (code) {
+ case KEY_ESC:
+ return 0;
+ case KEY_F1:
+ case KEY_F2:
+ case KEY_F3:
+ case KEY_F4:
+ case KEY_F5:
+ case KEY_F6:
+ case KEY_F7:
+ case KEY_F8:
+ case KEY_F9:
+ case KEY_F10:
+ return code - KEY_F1 + 1;
+ case KEY_F11:
+ case KEY_F12:
+ return code - KEY_F11 + 11;
+ default:
+ return -1;
+ }
+}
+
+static int appletb_hid_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+ unsigned int new_code = 0;
+ unsigned long flags;
+ bool send_dummy = false;
+ bool send_trnsl = false;
+ int slot;
+ int rc = 0;
+
+ /* Only interested in keyboard events */
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD ||
+ usage->type != EV_KEY)
+ return 0;
+
+ /*
+ * Skip non-touch-bar keys.
+ *
+ * Either the touch bar itself or usbhid generate a slew of key-down
+ * events for all the meta keys. None of which we're at all interested
+ * in.
+ */
+ slot = appletb_tb_key_to_slot(usage->code);
+ if (slot < 0)
+ return 0;
+
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ if (!tb_dev->active) {
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+ return 0;
+ }
+
+ new_code = appletb_fn_to_special(usage->code);
+
+ /* remember which (untranslated) touch bar keys are pressed */
+ if (value != 2)
+ tb_dev->last_tb_keys_pressed[slot] = value;
+
+ /* remember last time keyboard or touchpad was touched */
+ tb_dev->last_event_time = ktime_get();
+
+ /* only switch touch bar mode when no touch bar keys are pressed */
+ appletb_update_touchbar_no_lock(tb_dev, false);
+
+ /*
+ * We want to suppress touch bar keys while the touch bar is off, but
+ * we do want to wake up the screen if it's asleep, so generate a dummy
+ * event.
+ */
+ if (tb_dev->cur_tb_mode == APPLETB_CMD_MODE_OFF ||
+ tb_dev->cur_tb_disp == APPLETB_CMD_DISP_OFF) {
+ send_dummy = true;
+ rc = 1;
+ /* translate special keys */
+ } else if (new_code &&
+ ((value > 0 &&
+ appletb_get_cur_tb_mode(tb_dev) == APPLETB_CMD_MODE_SPCL)
+ ||
+ (value == 0 && tb_dev->last_tb_keys_translated[slot]))) {
+ tb_dev->last_tb_keys_translated[slot] = true;
+ send_trnsl = true;
+ rc = 1;
+ /* everything else handled normally */
+ } else {
+ tb_dev->last_tb_keys_translated[slot] = false;
+ }
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+ /*
+ * Need to send these input events outside of the lock, as otherwise
+ * we can run into the following deadlock:
+ * Task 1 Task 2
+ * appletb_hid_event() input_event()
+ * acquire tb_lock acquire dev->event_lock
+ * input_event() appletb_inp_event()
+ * acquire dev->event_lock acquire tb_lock
+ */
+ if (send_dummy) {
+ input_event(field->hidinput->input, EV_KEY, KEY_UNKNOWN, 1);
+ input_event(field->hidinput->input, EV_KEY, KEY_UNKNOWN, 0);
+ } else if (send_trnsl) {
+ input_event(field->hidinput->input, usage->type, new_code,
+ value);
+ }
+
+ return rc;
+}
+
+static void appletb_inp_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int value)
+{
+ struct appletb_device *tb_dev = handle->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ if (!tb_dev->active) {
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+ return;
+ }
+
+ /* remember last state of FN key */
+ if (type == EV_KEY && code == KEY_FN && value != 2)
+ tb_dev->last_fn_pressed = value;
+
+ /* remember last time keyboard or touchpad was touched */
+ tb_dev->last_event_time = ktime_get();
+
+ /* only switch touch bar mode when no touch bar keys are pressed */
+ appletb_update_touchbar_no_lock(tb_dev, false);
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+}
+
+/* Find and save the usb-device associated with the touch bar input device */
+static struct usb_interface *appletb_get_usb_iface(struct hid_device *hdev)
+{
+ struct device *dev = &hdev->dev;
+
+ /* find the usb-interface device */
+ if (!dev->bus || strcmp(dev->bus->name, "hid") != 0)
+ return NULL;
+
+ dev = dev->parent;
+ if (!dev || !dev->bus || strcmp(dev->bus->name, "usb") != 0)
+ return NULL;
+
+ return to_usb_interface(dev);
+}
+
+static int appletb_inp_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct appletb_device *tb_dev = handler->private;
+ struct input_handle *handle;
+ int rc;
+
+ if (id->driver_info == APPLETB_DEVID_KEYBOARD) {
+ handle = &tb_dev->kbd_handle;
+ handle->name = "tbkbd";
+ } else if (id->driver_info == APPLETB_DEVID_TOUCHPAD) {
+ handle = &tb_dev->tpd_handle;
+ handle->name = "tbtpad";
+ } else {
+ dev_err(tb_dev->log_dev, "Unknown device id (%lu)\n",
+ id->driver_info);
+ return -ENOENT;
+ }
+
+ if (handle->dev) {
+ dev_err(tb_dev->log_dev,
+ "Duplicate connect to %s input device\n", handle->name);
+ return -EEXIST;
+ }
+
+ handle->open = 0;
+ handle->dev = input_get_device(dev);
+ handle->handler = handler;
+ handle->private = tb_dev;
+
+ rc = input_register_handle(handle);
+ if (rc)
+ goto err_free_dev;
+
+ rc = input_open_device(handle);
+ if (rc)
+ goto err_unregister_handle;
+
+ dev_dbg(tb_dev->log_dev, "Connected to %s input device\n",
+ handle == &tb_dev->kbd_handle ? "keyboard" : "touchpad");
+
+ return 0;
+
+ err_unregister_handle:
+ input_unregister_handle(handle);
+ err_free_dev:
+ input_put_device(handle->dev);
+ handle->dev = NULL;
+ return rc;
+}
+
+static void appletb_inp_disconnect(struct input_handle *handle)
+{
+ struct appletb_device *tb_dev = handle->private;
+
+ input_close_device(handle);
+ input_unregister_handle(handle);
+
+ dev_dbg(tb_dev->log_dev, "Disconnected from %s input device\n",
+ handle == &tb_dev->kbd_handle ? "keyboard" : "touchpad");
+
+ input_put_device(handle->dev);
+ handle->dev = NULL;
+}
+
+static int appletb_input_configured(struct hid_device *hdev,
+ struct hid_input *hidinput)
+{
+ int idx;
+ struct input_dev *input = hidinput->input;
+
+ /*
+ * Clear various input capabilities that are blindly set by the hid
+ * driver (usbkbd.c)
+ */
+ memset(input->evbit, 0, sizeof(input->evbit));
+ memset(input->keybit, 0, sizeof(input->keybit));
+ memset(input->ledbit, 0, sizeof(input->ledbit));
+
+ /* set our actual capabilities */
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(EV_REP, input->evbit);
+ __set_bit(EV_MSC, input->evbit); /* hid-input generates MSC_SCAN */
+
+ for (idx = 0; idx < ARRAY_SIZE(appletb_fn_codes); idx++) {
+ input_set_capability(input, EV_KEY, appletb_fn_codes[idx].from);
+ input_set_capability(input, EV_KEY, appletb_fn_codes[idx].to);
+ }
+
+ input_set_capability(input, EV_KEY, KEY_ESC);
+ input_set_capability(input, EV_KEY, KEY_UNKNOWN);
+
+ return 0;
+}
+
+static int appletb_fill_report_info(struct appletb_device *tb_dev,
+ struct hid_device *hdev)
+{
+ struct appletb_report_info *report_info = NULL;
+ struct usb_interface *usb_iface;
+ struct hid_field *field;
+
+ field = appleib_find_hid_field(hdev, HID_GD_KEYBOARD, HID_USAGE_MODE);
+ if (field) {
+ report_info = &tb_dev->mode_info;
+ } else {
+ field = appleib_find_hid_field(hdev, HID_USAGE_APPLE_APP,
+ HID_USAGE_DISP);
+ if (field)
+ report_info = &tb_dev->disp_info;
+ }
+
+ if (!report_info)
+ return 0;
+
+ usb_iface = appletb_get_usb_iface(hdev);
+ if (!usb_iface) {
+ dev_err(tb_dev->log_dev,
+ "Failed to find usb interface for hid device %s\n",
+ dev_name(&hdev->dev));
+ return -ENODEV;
+ }
+
+ report_info->hdev = hdev;
+
+ report_info->usb_iface = usb_get_intf(usb_iface);
+ report_info->usb_epnum = 0;
+
+ report_info->report_id = field->report->id;
+ switch (field->report->type) {
+ case HID_INPUT_REPORT:
+ report_info->report_type = 0x01; break;
+ case HID_OUTPUT_REPORT:
+ report_info->report_type = 0x02; break;
+ case HID_FEATURE_REPORT:
+ report_info->report_type = 0x03; break;
+ }
+
+ return 1;
+}
+
+static struct appletb_report_info *
+appletb_get_report_info(struct appletb_device *tb_dev, struct hid_device *hdev)
+{
+ if (hdev == tb_dev->mode_info.hdev)
+ return &tb_dev->mode_info;
+ if (hdev == tb_dev->disp_info.hdev)
+ return &tb_dev->disp_info;
+ return NULL;
+}
+
+static void appletb_mark_active(struct appletb_device *tb_dev, bool active)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+ tb_dev->active = active;
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+}
+
+static const struct input_device_id appletb_input_devices[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_BUS |
+ INPUT_DEVICE_ID_MATCH_KEYBIT,
+ .bustype = BUS_SPI,
+ .keybit = { [BIT_WORD(KEY_FN)] = BIT_MASK(KEY_FN) },
+ .driver_info = APPLETB_DEVID_KEYBOARD,
+ }, /* Builtin keyboard device */
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_BUS |
+ INPUT_DEVICE_ID_MATCH_KEYBIT,
+ .bustype = BUS_SPI,
+ .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
+ .driver_info = APPLETB_DEVID_TOUCHPAD,
+ }, /* Builtin touchpad device */
+ { }, /* Terminating zero entry */
+};
+
+static int appletb_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+ struct appletb_report_info *report_info;
+ int rc;
+
+ /* initialize the report info */
+ rc = appletb_fill_report_info(tb_dev, hdev);
+ if (rc < 0)
+ goto error;
+
+ /* do setup if we have both interfaces */
+ if (tb_dev->mode_info.hdev && tb_dev->disp_info.hdev) {
+ /* mark active */
+ appletb_mark_active(tb_dev, true);
+
+ /* initialize the touch bar */
+ if (appletb_tb_def_fn_mode >= 0 &&
+ appletb_tb_def_fn_mode <= APPLETB_FN_MODE_MAX)
+ tb_dev->fn_mode = appletb_tb_def_fn_mode;
+ else
+ tb_dev->fn_mode = APPLETB_FN_MODE_NORM;
+ appletb_set_idle_timeout(tb_dev, appletb_tb_def_idle_timeout);
+ appletb_set_dim_timeout(tb_dev, appletb_tb_def_dim_timeout);
+ tb_dev->last_event_time = ktime_get();
+
+ tb_dev->cur_tb_mode = APPLETB_CMD_MODE_OFF;
+ tb_dev->cur_tb_disp = APPLETB_CMD_DISP_OFF;
+
+ appletb_update_touchbar(tb_dev, false);
+
+ /* set up the input handler */
+ tb_dev->inp_handler.event = appletb_inp_event;
+ tb_dev->inp_handler.connect = appletb_inp_connect;
+ tb_dev->inp_handler.disconnect = appletb_inp_disconnect;
+ tb_dev->inp_handler.name = "appletb";
+ tb_dev->inp_handler.id_table = appletb_input_devices;
+ tb_dev->inp_handler.private = tb_dev;
+
+ rc = input_register_handler(&tb_dev->inp_handler);
+ if (rc) {
+ dev_err(tb_dev->log_dev,
+ "Unable to register keyboard handler (%d)\n",
+ rc);
+ goto mark_inactive;
+ }
+
+ /* initialize sysfs attributes */
+ rc = sysfs_create_group(&tb_dev->mode_info.hdev->dev.kobj,
+ &appletb_attr_group);
+ if (rc) {
+ dev_err(tb_dev->log_dev,
+ "Failed to create sysfs attributes (%d)\n", rc);
+ goto unreg_handler;
+ }
+
+ dev_info(tb_dev->log_dev, "Touchbar activated\n");
+ }
+
+ return 0;
+
+unreg_handler:
+ input_unregister_handler(&tb_dev->inp_handler);
+mark_inactive:
+ appletb_mark_active(tb_dev, false);
+ cancel_delayed_work_sync(&tb_dev->tb_work);
+
+ report_info = appletb_get_report_info(tb_dev, hdev);
+ if (report_info) {
+ usb_put_intf(report_info->usb_iface);
+ report_info->usb_iface = NULL;
+ report_info->hdev = NULL;
+ }
+error:
+ return rc;
+}
+
+static void appletb_remove(struct hid_device *hdev)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+ struct appletb_report_info *report_info;
+
+ if ((hdev == tb_dev->mode_info.hdev && tb_dev->disp_info.hdev) ||
+ (hdev == tb_dev->disp_info.hdev && tb_dev->mode_info.hdev)) {
+ sysfs_remove_group(&tb_dev->mode_info.hdev->dev.kobj,
+ &appletb_attr_group);
+
+ input_unregister_handler(&tb_dev->inp_handler);
+
+ cancel_delayed_work_sync(&tb_dev->tb_work);
+ appletb_set_tb_mode(tb_dev, APPLETB_CMD_MODE_OFF);
+ appletb_set_tb_disp(tb_dev, APPLETB_CMD_DISP_ON);
+
+ if (tb_dev->tb_autopm_off)
+ usb_autopm_put_interface(tb_dev->disp_info.usb_iface);
+
+ appletb_mark_active(tb_dev, false);
+
+ dev_info(tb_dev->log_dev, "Touchbar deactivated\n");
+ }
+
+ report_info = appletb_get_report_info(tb_dev, hdev);
+ if (report_info) {
+ usb_put_intf(report_info->usb_iface);
+ report_info->usb_iface = NULL;
+ report_info->hdev = NULL;
+ }
+}
+
+#ifdef CONFIG_PM
+static int appletb_suspend(struct hid_device *hdev, pm_message_t message)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+ unsigned long flags;
+ bool all_suspended = false;
+
+ if (message.event != PM_EVENT_SUSPEND &&
+ message.event != PM_EVENT_FREEZE)
+ return 0;
+
+ /*
+ * Wait for both interfaces to be suspended and no more async work
+ * in progress.
+ */
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ if (!tb_dev->mode_info.suspended && !tb_dev->disp_info.suspended) {
+ tb_dev->active = false;
+ cancel_delayed_work(&tb_dev->tb_work);
+ }
+
+ appletb_get_report_info(tb_dev, hdev)->suspended = true;
+
+ if ((!tb_dev->mode_info.hdev || tb_dev->mode_info.suspended) &&
+ (!tb_dev->disp_info.hdev || tb_dev->disp_info.suspended))
+ all_suspended = true;
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+ flush_delayed_work(&tb_dev->tb_work);
+
+ if (!all_suspended)
+ return 0;
+
+ /*
+ * The touch bar device itself remembers the last state when suspended
+ * in some cases, but in others (e.g. when mode != off and disp == off)
+ * it resumes with a different state; furthermore it may be only
+ * partially responsive in that state. By turning both mode and disp
+ * off we ensure it is in a good state when resuming (and this happens
+ * to be the same state after booting/resuming-from-hibernate, so less
+ * special casing between the two).
+ */
+ if (message.event == PM_EVENT_SUSPEND) {
+ appletb_set_tb_mode(tb_dev, APPLETB_CMD_MODE_OFF);
+ appletb_set_tb_disp(tb_dev, APPLETB_CMD_DISP_OFF);
+ }
+
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ tb_dev->cur_tb_mode = APPLETB_CMD_MODE_OFF;
+ tb_dev->cur_tb_disp = APPLETB_CMD_DISP_OFF;
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+ dev_info(tb_dev->log_dev, "Touchbar suspended.\n");
+
+ return 0;
+}
+
+static int appletb_reset_resume(struct hid_device *hdev)
+{
+ struct appletb_device *tb_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev), &appletb_hid_driver);
+ unsigned long flags;
+
+ /*
+ * Restore touch bar state. Note that autopm state is preserved, no need
+ * explicitly restore that here.
+ */
+ spin_lock_irqsave(&tb_dev->tb_lock, flags);
+
+ appletb_get_report_info(tb_dev, hdev)->suspended = false;
+
+ if ((tb_dev->mode_info.hdev && !tb_dev->mode_info.suspended) &&
+ (tb_dev->disp_info.hdev && !tb_dev->disp_info.suspended)) {
+ tb_dev->active = true;
+ tb_dev->restore_autopm = true;
+ tb_dev->last_event_time = ktime_get();
+
+ appletb_update_touchbar_no_lock(tb_dev, true);
+
+ dev_info(tb_dev->log_dev, "Touchbar resumed.\n");
+ }
+
+ spin_unlock_irqrestore(&tb_dev->tb_lock, flags);
+
+ return 0;
+}
+#endif
+
+static struct appletb_device *appletb_alloc_device(struct device *log_dev)
+{
+ struct appletb_device *tb_dev;
+
+ /* allocate */
+ tb_dev = kzalloc(sizeof(*tb_dev), GFP_KERNEL);
+ if (!tb_dev)
+ return NULL;
+
+ /* initialize structures */
+ spin_lock_init(&tb_dev->tb_lock);
+ INIT_DELAYED_WORK(&tb_dev->tb_work, appletb_set_tb_worker);
+ tb_dev->log_dev = log_dev;
+
+ return tb_dev;
+}
+
+static void appletb_free_device(struct appletb_device *tb_dev)
+{
+ cancel_delayed_work_sync(&tb_dev->tb_work);
+ kfree(tb_dev);
+}
+
+static struct hid_driver appletb_hid_driver = {
+ .name = "apple-ib-touchbar",
+ .probe = appletb_probe,
+ .remove = appletb_remove,
+ .event = appletb_hid_event,
+ .input_configured = appletb_input_configured,
+#ifdef CONFIG_PM
+ .suspend = appletb_suspend,
+ .reset_resume = appletb_reset_resume,
+#endif
+};
+
+static int appletb_platform_probe(struct platform_device *pdev)
+{
+ struct appleib_platform_data *pdata = pdev->dev.platform_data;
+ struct appleib_device *ib_dev = pdata->ib_dev;
+ struct appletb_device *tb_dev;
+ int rc;
+
+ tb_dev = appletb_alloc_device(pdata->log_dev);
+ if (!tb_dev)
+ return -ENOMEM;
+
+ rc = appleib_register_hid_driver(ib_dev, &appletb_hid_driver, tb_dev);
+ if (rc) {
+ dev_err(tb_dev->log_dev, "Error registering hid driver: %d\n",
+ rc);
+ goto error;
+ }
+
+ platform_set_drvdata(pdev, tb_dev);
+
+ return 0;
+
+error:
+ appletb_free_device(tb_dev);
+ return rc;
+}
+
+static int appletb_platform_remove(struct platform_device *pdev)
+{
+ struct appleib_platform_data *pdata = pdev->dev.platform_data;
+ struct appleib_device *ib_dev = pdata->ib_dev;
+ struct appletb_device *tb_dev = platform_get_drvdata(pdev);
+ int rc;
+
+ rc = appleib_unregister_hid_driver(ib_dev, &appletb_hid_driver);
+ if (rc) {
+ dev_err(tb_dev->log_dev, "Error unregistering hid driver: %d\n",
+ rc);
+ goto error;
+ }
+
+ appletb_free_device(tb_dev);
+
+ return 0;
+
+error:
+ return rc;
+}
+
+static const struct platform_device_id appletb_platform_ids[] = {
+ { .name = PLAT_NAME_IB_TB },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, appletb_platform_ids);
+
+static struct platform_driver appletb_platform_driver = {
+ .id_table = appletb_platform_ids,
+ .driver = {
+ .name = "apple-ib-tb",
+ },
+ .probe = appletb_platform_probe,
+ .remove = appletb_platform_remove,
+};
+
+module_platform_driver(appletb_platform_driver);
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("MacBookPro Touch Bar driver");
+MODULE_LICENSE("GPL v2");
--
2.20.1
^ permalink raw reply related
* [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
From: Ronald Tschalär @ 2019-04-22 3:12 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones
Cc: linux-input, linux-iio, linux-kernel
In-Reply-To: <20190422031251.11968-1-ronald@innovation.ch>
On 2016/2017 MacBook Pro's with a Touch Bar the ALS is attached to,
and exposed via the iBridge device. This provides the driver for that
sensor.
Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
drivers/iio/light/Kconfig | 12 +
drivers/iio/light/Makefile | 1 +
drivers/iio/light/apple-ib-als.c | 694 +++++++++++++++++++++++++++++++
3 files changed, 707 insertions(+)
create mode 100644 drivers/iio/light/apple-ib-als.c
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 36f458433480..49159fab1c0e 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -64,6 +64,18 @@ config APDS9960
To compile this driver as a module, choose M here: the
module will be called apds9960
+config APPLE_IBRIDGE_ALS
+ tristate "Apple iBridge ambient light sensor"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ depends on MFD_APPLE_IBRIDGE
+ help
+ Say Y here to build the driver for the Apple iBridge ALS
+ sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called apple-ib-als.
+
config BH1750
tristate "ROHM BH1750 ambient light sensor"
depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 286bf3975372..144d918917f7 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o
obj-$(CONFIG_AL3320A) += al3320a.o
obj-$(CONFIG_APDS9300) += apds9300.o
obj-$(CONFIG_APDS9960) += apds9960.o
+obj-$(CONFIG_APPLE_IBRIDGE_ALS) += apple-ib-als.o
obj-$(CONFIG_BH1750) += bh1750.o
obj-$(CONFIG_BH1780) += bh1780.o
obj-$(CONFIG_CM32181) += cm32181.o
diff --git a/drivers/iio/light/apple-ib-als.c b/drivers/iio/light/apple-ib-als.c
new file mode 100644
index 000000000000..1718fcbe304f
--- /dev/null
+++ b/drivers/iio/light/apple-ib-als.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Ambient Light Sensor Driver
+ *
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ */
+
+/*
+ * MacBookPro models with an iBridge chip (13,[23] and 14,[23]) have an
+ * ambient light sensor that is exposed via one of the USB interfaces on
+ * the iBridge as a standard HID light sensor. However, we cannot use the
+ * existing hid-sensor-als driver, for two reasons:
+ *
+ * 1. The hid-sensor-als driver is part of the hid-sensor-hub which in turn
+ * is a hid driver, but you can't have more than one hid driver per hid
+ * device, which is a problem because the touch bar also needs to
+ * register as a driver for this hid device.
+ *
+ * 2. While the hid-sensors-als driver stores sensor readings received via
+ * interrupt in an iio buffer, reads on the sysfs
+ * .../iio:deviceX/in_illuminance_YYY attribute result in a get of the
+ * feature report; however, in the case of this sensor here the
+ * illuminance field of that report is always 0. Instead, the input
+ * report needs to be requested.
+ */
+
+#define dev_fmt(fmt) "als: " fmt
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/hid-sensor-ids.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/mfd/apple-ibridge.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define APPLEALS_DYN_SENS 0 /* our dynamic sensitivity */
+#define APPLEALS_DEF_CHANGE_SENS APPLEALS_DYN_SENS
+
+struct appleals_device {
+ struct appleib_device *ib_dev;
+ struct device *log_dev;
+ struct hid_device *hid_dev;
+ struct hid_report *cfg_report;
+ struct hid_field *illum_field;
+ struct iio_dev *iio_dev;
+ struct iio_trigger *iio_trig;
+ int cur_sensitivity;
+ int cur_hysteresis;
+ bool events_enabled;
+};
+
+static struct hid_driver appleals_hid_driver;
+
+/*
+ * This is a primitive way to get a relative sensitivity, one where we get
+ * notified when the value changes by a certain percentage rather than some
+ * absolute value. MacOS somehow manages to configure the sensor to work this
+ * way (with a 15% relative sensitivity), but I haven't been able to figure
+ * out how so far. So until we do, this provides a less-than-perfect
+ * simulation.
+ *
+ * When the brightness value is within one of the ranges, the sensitivity is
+ * set to that range's sensitivity. But in order to reduce flapping when the
+ * brightness is right on the border between two ranges, the ranges overlap
+ * somewhat (by at least one sensitivity), and sensitivity is only changed if
+ * the value leaves the current sensitivity's range.
+ *
+ * The values chosen for the map are somewhat arbitrary: a compromise of not
+ * too many ranges (and hence changing the sensitivity) but not too small or
+ * large of a percentage of the min and max values in the range (currently
+ * from 7.5% to 30%, i.e. within a factor of 2 of 15%), as well as just plain
+ * "this feels reasonable to me".
+ */
+struct appleals_sensitivity_map {
+ int sensitivity;
+ int illum_low;
+ int illum_high;
+};
+
+static struct appleals_sensitivity_map appleals_sensitivity_map[] = {
+ { 1, 0, 14 },
+ { 3, 10, 40 },
+ { 9, 30, 120 },
+ { 27, 90, 360 },
+ { 81, 270, 1080 },
+ { 243, 810, 3240 },
+ { 729, 2430, 9720 },
+};
+
+static int appleals_compute_sensitivity(int cur_illum, int cur_sens)
+{
+ struct appleals_sensitivity_map *entry;
+ int i;
+
+ /* see if we're still in current range */
+ for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
+ entry = &appleals_sensitivity_map[i];
+
+ if (entry->sensitivity == cur_sens &&
+ entry->illum_low <= cur_illum &&
+ entry->illum_high >= cur_illum)
+ return cur_sens;
+ else if (entry->sensitivity > cur_sens)
+ break;
+ }
+
+ /* not in current range, so find new sensitivity */
+ for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
+ entry = &appleals_sensitivity_map[i];
+
+ if (entry->illum_low <= cur_illum &&
+ entry->illum_high >= cur_illum)
+ return entry->sensitivity;
+ }
+
+ /* hmm, not in table, so assume we are above highest range */
+ i = ARRAY_SIZE(appleals_sensitivity_map) - 1;
+ return appleals_sensitivity_map[i].sensitivity;
+}
+
+static int appleals_get_field_value_for_usage(struct hid_field *field,
+ unsigned int usage)
+{
+ int u;
+
+ if (!field)
+ return -1;
+
+ for (u = 0; u < field->maxusage; u++) {
+ if (field->usage[u].hid == usage)
+ return u + field->logical_minimum;
+ }
+
+ return -1;
+}
+
+static __s32 appleals_get_field_value(struct appleals_device *als_dev,
+ struct hid_field *field)
+{
+ hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_GET_REPORT);
+ hid_hw_wait(als_dev->hid_dev);
+
+ return field->value[0];
+}
+
+static void appleals_set_field_value(struct appleals_device *als_dev,
+ struct hid_field *field, __s32 value)
+{
+ hid_set_field(field, 0, value);
+ hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_SET_REPORT);
+}
+
+static int appleals_get_config(struct appleals_device *als_dev,
+ unsigned int field_usage, __s32 *value)
+{
+ struct hid_field *field;
+
+ field = appleib_find_report_field(als_dev->cfg_report, field_usage);
+ if (!field)
+ return -EINVAL;
+
+ *value = appleals_get_field_value(als_dev, field);
+
+ return 0;
+}
+
+static int appleals_set_config(struct appleals_device *als_dev,
+ unsigned int field_usage, __s32 value)
+{
+ struct hid_field *field;
+
+ field = appleib_find_report_field(als_dev->cfg_report, field_usage);
+ if (!field)
+ return -EINVAL;
+
+ appleals_set_field_value(als_dev, field, value);
+
+ return 0;
+}
+
+static int appleals_set_enum_config(struct appleals_device *als_dev,
+ unsigned int field_usage,
+ unsigned int value_usage)
+{
+ struct hid_field *field;
+ int value;
+
+ field = appleib_find_report_field(als_dev->cfg_report, field_usage);
+ if (!field)
+ return -EINVAL;
+
+ value = appleals_get_field_value_for_usage(field, value_usage);
+
+ appleals_set_field_value(als_dev, field, value);
+
+ return 0;
+}
+
+static void appleals_update_dyn_sensitivity(struct appleals_device *als_dev,
+ __s32 value)
+{
+ int new_sens;
+ int rc;
+
+ new_sens = appleals_compute_sensitivity(value,
+ als_dev->cur_sensitivity);
+ if (new_sens != als_dev->cur_sensitivity) {
+ rc = appleals_set_config(als_dev,
+ HID_USAGE_SENSOR_LIGHT_ILLUM |
+ HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
+ new_sens);
+ if (!rc)
+ als_dev->cur_sensitivity = new_sens;
+ }
+}
+
+static void appleals_push_new_value(struct appleals_device *als_dev,
+ __s32 value)
+{
+ __s32 buf[2] = { value, value };
+
+ iio_push_to_buffers(als_dev->iio_dev, buf);
+
+ if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS)
+ appleals_update_dyn_sensitivity(als_dev, value);
+}
+
+static int appleals_hid_event(struct hid_device *hdev, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ struct appleals_device *als_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev),
+ &appleals_hid_driver);
+ int rc = 0;
+
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_SENSOR)
+ return 0;
+
+ if (usage->hid == HID_USAGE_SENSOR_LIGHT_ILLUM) {
+ appleals_push_new_value(als_dev, value);
+ rc = 1;
+ }
+
+ return rc;
+}
+
+static int appleals_enable_events(struct iio_trigger *trig, bool enable)
+{
+ struct appleals_device *als_dev = iio_trigger_get_drvdata(trig);
+ int value;
+
+ /* set the sensor's reporting state */
+ appleals_set_enum_config(als_dev, HID_USAGE_SENSOR_PROP_REPORT_STATE,
+ enable ? HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
+ HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
+ als_dev->events_enabled = enable;
+
+ /* if the sensor was enabled, push an initial value */
+ if (enable) {
+ value = appleals_get_field_value(als_dev, als_dev->illum_field);
+ appleals_push_new_value(als_dev, value);
+ }
+
+ return 0;
+}
+
+static int appleals_read_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct appleals_device *als_dev =
+ *(struct appleals_device **)iio_priv(iio_dev);
+ __s32 value;
+ int rc;
+
+ *val = 0;
+ *val2 = 0;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_PROCESSED:
+ *val = appleals_get_field_value(als_dev, als_dev->illum_field);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ rc = appleals_get_config(als_dev,
+ HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
+ &value);
+ if (rc)
+ return rc;
+
+ /* interval is in ms; val is in HZ, val2 in µHZ */
+ value = 1000000000 / value;
+ *val = value / 1000000;
+ *val2 = value - (*val * 1000000);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+
+ case IIO_CHAN_INFO_HYSTERESIS:
+ if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS) {
+ *val = als_dev->cur_hysteresis;
+ return IIO_VAL_INT;
+ }
+
+ rc = appleals_get_config(als_dev,
+ HID_USAGE_SENSOR_LIGHT_ILLUM |
+ HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
+ val);
+ if (!rc) {
+ als_dev->cur_sensitivity = *val;
+ als_dev->cur_hysteresis = *val;
+ }
+ return rc ? rc : IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int appleals_write_raw(struct iio_dev *iio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct appleals_device *als_dev =
+ *(struct appleals_device **)iio_priv(iio_dev);
+ __s32 illum;
+ int rc;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ rc = appleals_set_config(als_dev,
+ HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
+ 1000000000 / (val * 1000000 + val2));
+ break;
+
+ case IIO_CHAN_INFO_HYSTERESIS:
+ if (val == APPLEALS_DYN_SENS) {
+ if (als_dev->cur_hysteresis != APPLEALS_DYN_SENS) {
+ als_dev->cur_hysteresis = val;
+ illum = appleals_get_field_value(als_dev,
+ als_dev->illum_field);
+ appleals_update_dyn_sensitivity(als_dev, illum);
+ }
+ rc = 0;
+ break;
+ }
+
+ rc = appleals_set_config(als_dev,
+ HID_USAGE_SENSOR_LIGHT_ILLUM |
+ HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
+ val);
+ if (!rc) {
+ als_dev->cur_sensitivity = val;
+ als_dev->cur_hysteresis = val;
+ }
+ break;
+
+ default:
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static const struct iio_chan_spec appleals_channels[] = {
+ {
+ .type = IIO_INTENSITY,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_BOTH,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 32,
+ .storagebits = 32,
+ },
+ .scan_index = 0,
+ },
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 32,
+ .storagebits = 32,
+ },
+ .scan_index = 1,
+ }
+};
+
+static const struct iio_trigger_ops appleals_trigger_ops = {
+ .set_trigger_state = &appleals_enable_events,
+};
+
+static const struct iio_info appleals_info = {
+ .read_raw = &appleals_read_raw,
+ .write_raw = &appleals_write_raw,
+};
+
+static void appleals_config_sensor(struct appleals_device *als_dev,
+ bool events_enabled, int sensitivity)
+{
+ struct hid_field *field;
+ __s32 val;
+
+ /*
+ * We're (often) in a probe here, so need to enable input processing
+ * in that case, but only in that case.
+ */
+ if (appleib_in_hid_probe(als_dev->ib_dev))
+ hid_device_io_start(als_dev->hid_dev);
+
+ /* power on the sensor */
+ field = appleib_find_report_field(als_dev->cfg_report,
+ HID_USAGE_SENSOR_PROY_POWER_STATE);
+ val = appleals_get_field_value_for_usage(field,
+ HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM);
+ hid_set_field(field, 0, val);
+
+ /* configure reporting of change events */
+ field = appleib_find_report_field(als_dev->cfg_report,
+ HID_USAGE_SENSOR_PROP_REPORT_STATE);
+ val = appleals_get_field_value_for_usage(field,
+ events_enabled ?
+ HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
+ HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
+ hid_set_field(field, 0, val);
+
+ /* report change events asap */
+ field = appleib_find_report_field(als_dev->cfg_report,
+ HID_USAGE_SENSOR_PROP_REPORT_INTERVAL);
+ hid_set_field(field, 0, field->logical_minimum);
+
+ /*
+ * Set initial change sensitivity; if dynamic, enabling trigger will set
+ * it instead.
+ */
+ if (sensitivity != APPLEALS_DYN_SENS) {
+ field = appleib_find_report_field(als_dev->cfg_report,
+ HID_USAGE_SENSOR_LIGHT_ILLUM |
+ HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS);
+
+ hid_set_field(field, 0, sensitivity);
+ }
+
+ /* write the new config to the sensor */
+ hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
+ HID_REQ_SET_REPORT);
+
+ if (appleib_in_hid_probe(als_dev->ib_dev))
+ hid_device_io_stop(als_dev->hid_dev);
+};
+
+static int appleals_config_iio(struct appleals_device *als_dev)
+{
+ struct iio_dev *iio_dev;
+ struct iio_trigger *iio_trig;
+ int rc;
+
+ /* create and register iio device */
+ iio_dev = iio_device_alloc(sizeof(als_dev));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ *(struct appleals_device **)iio_priv(iio_dev) = als_dev;
+
+ iio_dev->channels = appleals_channels;
+ iio_dev->num_channels = ARRAY_SIZE(appleals_channels);
+ iio_dev->dev.parent = &als_dev->hid_dev->dev;
+ iio_dev->info = &appleals_info;
+ iio_dev->name = "als";
+ iio_dev->modes = INDIO_DIRECT_MODE;
+
+ rc = iio_triggered_buffer_setup(iio_dev, &iio_pollfunc_store_time, NULL,
+ NULL);
+ if (rc) {
+ dev_err(als_dev->log_dev, "failed to set up iio triggers: %d\n",
+ rc);
+ goto free_iio_dev;
+ }
+
+ iio_trig = iio_trigger_alloc("%s-dev%d", iio_dev->name, iio_dev->id);
+ if (!iio_trig) {
+ rc = -ENOMEM;
+ goto clean_trig_buf;
+ }
+
+ iio_trig->dev.parent = &als_dev->hid_dev->dev;
+ iio_trig->ops = &appleals_trigger_ops;
+ iio_trigger_set_drvdata(iio_trig, als_dev);
+
+ rc = iio_trigger_register(iio_trig);
+ if (rc) {
+ dev_err(als_dev->log_dev, "failed to register iio trigger: %d\n",
+ rc);
+ goto free_iio_trig;
+ }
+
+ als_dev->iio_trig = iio_trig;
+
+ rc = iio_device_register(iio_dev);
+ if (rc) {
+ dev_err(als_dev->log_dev, "failed to register iio device: %d\n",
+ rc);
+ goto unreg_iio_trig;
+ }
+
+ als_dev->iio_dev = iio_dev;
+
+ return 0;
+
+unreg_iio_trig:
+ iio_trigger_unregister(iio_trig);
+free_iio_trig:
+ iio_trigger_free(iio_trig);
+ als_dev->iio_trig = NULL;
+clean_trig_buf:
+ iio_triggered_buffer_cleanup(iio_dev);
+free_iio_dev:
+ iio_device_free(iio_dev);
+
+ return rc;
+}
+
+static int appleals_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ struct appleals_device *als_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev),
+ &appleals_hid_driver);
+ struct hid_field *state_field;
+ struct hid_field *illum_field;
+ int rc;
+
+ /* find als fields and reports */
+ state_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
+ HID_USAGE_SENSOR_PROP_REPORT_STATE);
+ illum_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
+ HID_USAGE_SENSOR_LIGHT_ILLUM);
+ if (!state_field || !illum_field)
+ return -ENODEV;
+
+ if (als_dev->hid_dev) {
+ dev_warn(als_dev->log_dev,
+ "Found duplicate ambient light sensor - ignoring\n");
+ return -EBUSY;
+ }
+
+ dev_info(als_dev->log_dev, "Found ambient light sensor\n");
+
+ /* initialize device */
+ als_dev->hid_dev = hdev;
+ als_dev->cfg_report = state_field->report;
+ als_dev->illum_field = illum_field;
+
+ als_dev->cur_hysteresis = APPLEALS_DEF_CHANGE_SENS;
+ als_dev->cur_sensitivity = APPLEALS_DEF_CHANGE_SENS;
+ appleals_config_sensor(als_dev, false, als_dev->cur_sensitivity);
+
+ rc = appleals_config_iio(als_dev);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static void appleals_remove(struct hid_device *hdev)
+{
+ struct appleals_device *als_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev),
+ &appleals_hid_driver);
+
+ if (als_dev->iio_dev) {
+ iio_device_unregister(als_dev->iio_dev);
+
+ iio_trigger_unregister(als_dev->iio_trig);
+ iio_trigger_free(als_dev->iio_trig);
+ als_dev->iio_trig = NULL;
+
+ iio_triggered_buffer_cleanup(als_dev->iio_dev);
+ iio_device_free(als_dev->iio_dev);
+ als_dev->iio_dev = NULL;
+ }
+
+ als_dev->hid_dev = NULL;
+}
+
+#ifdef CONFIG_PM
+static int appleals_reset_resume(struct hid_device *hdev)
+{
+ struct appleals_device *als_dev =
+ appleib_get_drvdata(hid_get_drvdata(hdev),
+ &appleals_hid_driver);
+
+ appleals_config_sensor(als_dev, als_dev->events_enabled,
+ als_dev->cur_sensitivity);
+
+ return 0;
+}
+#endif
+
+static struct hid_driver appleals_hid_driver = {
+ .name = "apple-ib-als",
+ .probe = appleals_probe,
+ .remove = appleals_remove,
+ .event = appleals_hid_event,
+#ifdef CONFIG_PM
+ .reset_resume = appleals_reset_resume,
+#endif
+};
+
+static int appleals_platform_probe(struct platform_device *pdev)
+{
+ struct appleib_platform_data *pdata = pdev->dev.platform_data;
+ struct appleib_device *ib_dev = pdata->ib_dev;
+ struct appleals_device *als_dev;
+ int rc;
+
+ als_dev = kzalloc(sizeof(*als_dev), GFP_KERNEL);
+ if (!als_dev)
+ return -ENOMEM;
+
+ als_dev->ib_dev = ib_dev;
+ als_dev->log_dev = pdata->log_dev;
+
+ rc = appleib_register_hid_driver(ib_dev, &appleals_hid_driver, als_dev);
+ if (rc) {
+ dev_err(als_dev->log_dev, "Error registering hid driver: %d\n",
+ rc);
+ goto error;
+ }
+
+ platform_set_drvdata(pdev, als_dev);
+
+ return 0;
+
+error:
+ kfree(als_dev);
+ return rc;
+}
+
+static int appleals_platform_remove(struct platform_device *pdev)
+{
+ struct appleib_platform_data *pdata = pdev->dev.platform_data;
+ struct appleib_device *ib_dev = pdata->ib_dev;
+ struct appleals_device *als_dev = platform_get_drvdata(pdev);
+ int rc;
+
+ rc = appleib_unregister_hid_driver(ib_dev, &appleals_hid_driver);
+ if (rc) {
+ dev_err(als_dev->log_dev,
+ "Error unregistering hid driver: %d\n", rc);
+ goto error;
+ }
+
+ kfree(als_dev);
+
+ return 0;
+
+error:
+ return rc;
+}
+
+static const struct platform_device_id appleals_platform_ids[] = {
+ { .name = PLAT_NAME_IB_ALS },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, appleals_platform_ids);
+
+static struct platform_driver appleals_platform_driver = {
+ .id_table = appleals_platform_ids,
+ .driver = {
+ .name = "apple-ib-als",
+ },
+ .probe = appleals_platform_probe,
+ .remove = appleals_platform_remove,
+};
+
+module_platform_driver(appleals_platform_driver);
+
+MODULE_AUTHOR("Ronald Tschalär");
+MODULE_DESCRIPTION("Apple iBridge ALS driver");
+MODULE_LICENSE("GPL v2");
--
2.20.1
^ permalink raw reply related
* [PATCH] HID: i2c-hid: add iBall Aer3 to descriptor override
From: Kai-Heng Feng @ 2019-04-22 6:57 UTC (permalink / raw)
To: jikos, benjamin.tissoires; +Cc: linux-input, linux-kernel, Kai-Heng Feng
This device uses the SIPODEV SP1064 touchpad, which does not
supply descriptors, so it has to be added to the override
list.
BugLink: https://bugs.launchpad.net/bugs/1825718
Signed-off-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
---
drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
index fd1b6eea6d2f..75078c83be1a 100644
--- a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
+++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
@@ -354,6 +354,14 @@ static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = {
},
.driver_data = (void *)&sipodev_desc
},
+ {
+ .ident = "iBall Aer3",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "iBall"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Aer3"),
+ },
+ .driver_data = (void *)&sipodev_desc
+ },
{ } /* Terminate list */
};
--
2.17.1
^ permalink raw reply related
* Re: [PATCH 3/3] iio: Add PAT9125 optical tracker sensor
From: Jonathan Cameron @ 2019-04-22 8:42 UTC (permalink / raw)
To: Alexandre
Cc: robh+dt, mark.rutland, knaack.h, lars, pmeerw, linux-kernel,
linux-iio, baylibre-upstreaming, Dmitry Torokhov, linux-input
In-Reply-To: <f8ffca5b-09a8-cad7-4561-bf1263388228@baylibre.com>
On Tue, 16 Apr 2019 14:49:19 +0200
Alexandre <amergnat@baylibre.com> wrote:
> Hello Jonathan,
>
> On 4/7/19 12:20, Jonathan Cameron wrote:
> > Hi Alexandre,
> >
> > So I have no problem with this as an IIO driver, but for devices that
> > are somewhat 'on the edge' I always like to get a clear answer to the
> > question: Why not input?
> >
> > I would also argue that, to actually be 'useful' we would typically need
> > some representation of the 'mechanicals' that are providing the motion
> > being measured. Looking at the datasheet this includes, rotating shafts
> > (side or end on), disk edges and flat surface tracking (mouse like).
> >
> > That's easy enough to do with the iio in kernel consumer interface. These
> > are similar to when we handle analog electronic front ends.
> >
> > I you can, please describe what it is being used for in your application
> > as that may give us somewhere to start!
> >
> > + CC Dmitry and linux-input.
>
> I developed this driver to detect the board movement which can't be
> detected by accelerometer (very slow motion). I admit this use case can
> be handled by an input, and I'm agree with you, PAT9125 driver could be
> an input. But, like you said, this chip is able to track different kind
> of motion, and additionally have an interrupt GPIO, so using it like
> input limit the driver potential. This chip is designed to work in
> industrial measurement or embedded systems, and the IIO API match with
> these environments, so it's the best way to exploit the entire potential
> of this chip.
>
> As I understand (from
> https://www.kernel.org/doc/html/v4.12/input/event-codes.html#mice ),
> mouse driver must report values when the device move. This feature
> souldn't be mandatory for an optical tracker driver, specially for cases
> where user prefers to use buffer or poll only when he need data.
>
> > If 1 or 2, I would suggest that you provide absolute position to
> > Linux. So add the value to a software counter and provide that.
> > 32 bits should be plenty of resolution for that.
> I can't provide absolute position, only relative. Do you mean using
> input driver to do that ? If not, how is built the position data?
Sorry, I should have been clearer on this.
I mean absolute relative to the start point. So on startup you assume
absolute position is 0 and go from there. What I can't work out is
if the device does internal tracking, or whether each time you read
it effectively resets it's internal counters to 0 so the next measurement
is relative to the previous one.
>
> > Silly question for you. What happens if you set the delta values to 0?
> > Do we get an interrupt which is effectively data ready?
> > If we do, you might want to think about a scheme where that is an option.
> > As things currently stand we have a confusing interface where changing this
> > threshold effects the buffered data output. That should only be the
> > case if this interface is for a trigger, not an event.
>
> I'm not sure to understand your question. Is it possible to read delta_x
> and delta_y = 0 in special/corner case because internal value continue
> to be updated after toggled motion_detect pin (used for IRQ) until
> values registers are read and then motion_detect pin is released:
>
> * Chip move (i.e. +2 on X axis and 0 on Y axis)
> * Motion_detect IRQ trigger and internal reg value is updated (i.e.
> delta_x = 2 and delta_y = 0.
> * GPIO IRQ handled but read_value isn't executed yet (timing reason)
> * Chip move back to it origin point (i.e. -2 on X axis and 0 on Y axis)
> * Motion_detect IRQ still low because it hasn't been reset by read
> value and internal reg value is updated (i.e. delta_x = 0 and
> delta_y = 0)
> * Read_value is executed, we get delta values = 0.
Again, I was unclear. Is it possible to set the device to interrupt
every time it evaluates whether motion has occured? Not only when it
concludes that there has been some motion. That would allow the interrupt
to be used as a signal that the device has taken a measurement (data
ready signal in other sensors).
>
> > If it is actually not possible to report the two channels separately
> > then don't report them at all except via the buffered interface and
> > set the available scan masks so that both are on.
> I found a way to keep the consistency between delta x and delta y
> (without losing data). The first part is to reset a value only when user
> read it (also when it's buffered). The second part is to add the new
> value to the old value. With these two mechanism, X and Y will always be
> consistent:
>
> * as possible during a move.
> * perfectly when move is finished.
Ah. This adding old value to a new value point is what I was getting
at (I think) with 'absolute' position above.
In industrial control for example you have absolute position by using
limit switches to set your baseline. Measurement devices are then
capable of either reporting relative position, which is the movement
since the last reading was taken, or 'absolute' position which is
referenced to some known point. It was this form of absolute position
that I was suggesting you use. If you use such a system without a
limit switch it is normally called unreference motion. You can do
it but then the 0 is where ever your device was at power on.
For some systems it doesn't actually matter (conveyor belts for
instance where the positions you care about are between things
on the belt, not the position of the belt itself).
Thanks,
Jonathan
>
>
> Regards,
>
> Alexandre
>
^ permalink raw reply
* Re: [PATCH 3/3] iio: light: apple-ib-als: Add driver for ALS on iBridge chip.
From: Peter Meerwald-Stadler @ 2019-04-22 9:17 UTC (permalink / raw)
To: Ronald Tschalär
Cc: Jiri Kosina, Benjamin Tissoires, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Lee Jones, linux-input, linux-iio,
linux-kernel
In-Reply-To: <20190422031251.11968-4-ronald@innovation.ch>
[-- Attachment #1: Type: text/plain, Size: 22811 bytes --]
On Sun, 21 Apr 2019, Ronald Tschalär wrote:
> On 2016/2017 MacBook Pro's with a Touch Bar the ALS is attached to,
> and exposed via the iBridge device. This provides the driver for that
> sensor.
some comments below inline
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
> ---
> drivers/iio/light/Kconfig | 12 +
> drivers/iio/light/Makefile | 1 +
> drivers/iio/light/apple-ib-als.c | 694 +++++++++++++++++++++++++++++++
> 3 files changed, 707 insertions(+)
> create mode 100644 drivers/iio/light/apple-ib-als.c
>
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> index 36f458433480..49159fab1c0e 100644
> --- a/drivers/iio/light/Kconfig
> +++ b/drivers/iio/light/Kconfig
> @@ -64,6 +64,18 @@ config APDS9960
> To compile this driver as a module, choose M here: the
> module will be called apds9960
>
> +config APPLE_IBRIDGE_ALS
> + tristate "Apple iBridge ambient light sensor"
> + select IIO_BUFFER
> + select IIO_TRIGGERED_BUFFER
> + depends on MFD_APPLE_IBRIDGE
> + help
> + Say Y here to build the driver for the Apple iBridge ALS
> + sensor.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called apple-ib-als.
> +
> config BH1750
> tristate "ROHM BH1750 ambient light sensor"
> depends on I2C
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> index 286bf3975372..144d918917f7 100644
> --- a/drivers/iio/light/Makefile
> +++ b/drivers/iio/light/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o
> obj-$(CONFIG_AL3320A) += al3320a.o
> obj-$(CONFIG_APDS9300) += apds9300.o
> obj-$(CONFIG_APDS9960) += apds9960.o
> +obj-$(CONFIG_APPLE_IBRIDGE_ALS) += apple-ib-als.o
> obj-$(CONFIG_BH1750) += bh1750.o
> obj-$(CONFIG_BH1780) += bh1780.o
> obj-$(CONFIG_CM32181) += cm32181.o
> diff --git a/drivers/iio/light/apple-ib-als.c b/drivers/iio/light/apple-ib-als.c
> new file mode 100644
> index 000000000000..1718fcbe304f
> --- /dev/null
> +++ b/drivers/iio/light/apple-ib-als.c
> @@ -0,0 +1,694 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple Ambient Light Sensor Driver
> + *
> + * Copyright (c) 2017-2018 Ronald Tschalär
> + */
> +
> +/*
> + * MacBookPro models with an iBridge chip (13,[23] and 14,[23]) have an
> + * ambient light sensor that is exposed via one of the USB interfaces on
> + * the iBridge as a standard HID light sensor. However, we cannot use the
> + * existing hid-sensor-als driver, for two reasons:
> + *
> + * 1. The hid-sensor-als driver is part of the hid-sensor-hub which in turn
> + * is a hid driver, but you can't have more than one hid driver per hid
> + * device, which is a problem because the touch bar also needs to
> + * register as a driver for this hid device.
> + *
> + * 2. While the hid-sensors-als driver stores sensor readings received via
> + * interrupt in an iio buffer, reads on the sysfs
> + * .../iio:deviceX/in_illuminance_YYY attribute result in a get of the
> + * feature report; however, in the case of this sensor here the
> + * illuminance field of that report is always 0. Instead, the input
> + * report needs to be requested.
> + */
> +
> +#define dev_fmt(fmt) "als: " fmt
> +
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/hid-sensor-ids.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/mfd/apple-ibridge.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#define APPLEALS_DYN_SENS 0 /* our dynamic sensitivity */
> +#define APPLEALS_DEF_CHANGE_SENS APPLEALS_DYN_SENS
> +
> +struct appleals_device {
> + struct appleib_device *ib_dev;
> + struct device *log_dev;
> + struct hid_device *hid_dev;
> + struct hid_report *cfg_report;
> + struct hid_field *illum_field;
> + struct iio_dev *iio_dev;
> + struct iio_trigger *iio_trig;
> + int cur_sensitivity;
> + int cur_hysteresis;
> + bool events_enabled;
> +};
> +
> +static struct hid_driver appleals_hid_driver;
> +
> +/*
> + * This is a primitive way to get a relative sensitivity, one where we get
> + * notified when the value changes by a certain percentage rather than some
> + * absolute value. MacOS somehow manages to configure the sensor to work this
> + * way (with a 15% relative sensitivity), but I haven't been able to figure
> + * out how so far. So until we do, this provides a less-than-perfect
> + * simulation.
> + *
> + * When the brightness value is within one of the ranges, the sensitivity is
> + * set to that range's sensitivity. But in order to reduce flapping when the
> + * brightness is right on the border between two ranges, the ranges overlap
> + * somewhat (by at least one sensitivity), and sensitivity is only changed if
> + * the value leaves the current sensitivity's range.
> + *
> + * The values chosen for the map are somewhat arbitrary: a compromise of not
> + * too many ranges (and hence changing the sensitivity) but not too small or
> + * large of a percentage of the min and max values in the range (currently
> + * from 7.5% to 30%, i.e. within a factor of 2 of 15%), as well as just plain
> + * "this feels reasonable to me".
> + */
> +struct appleals_sensitivity_map {
> + int sensitivity;
> + int illum_low;
> + int illum_high;
> +};
> +
> +static struct appleals_sensitivity_map appleals_sensitivity_map[] = {
const?
> + { 1, 0, 14 },
> + { 3, 10, 40 },
> + { 9, 30, 120 },
> + { 27, 90, 360 },
> + { 81, 270, 1080 },
> + { 243, 810, 3240 },
> + { 729, 2430, 9720 },
> +};
> +
> +static int appleals_compute_sensitivity(int cur_illum, int cur_sens)
> +{
> + struct appleals_sensitivity_map *entry;
> + int i;
> +
> + /* see if we're still in current range */
> + for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
> + entry = &appleals_sensitivity_map[i];
> +
> + if (entry->sensitivity == cur_sens &&
> + entry->illum_low <= cur_illum &&
> + entry->illum_high >= cur_illum)
> + return cur_sens;
> + else if (entry->sensitivity > cur_sens)
> + break;
> + }
> +
> + /* not in current range, so find new sensitivity */
> + for (i = 0; i < ARRAY_SIZE(appleals_sensitivity_map); i++) {
> + entry = &appleals_sensitivity_map[i];
> +
> + if (entry->illum_low <= cur_illum &&
> + entry->illum_high >= cur_illum)
> + return entry->sensitivity;
> + }
> +
> + /* hmm, not in table, so assume we are above highest range */
> + i = ARRAY_SIZE(appleals_sensitivity_map) - 1;
> + return appleals_sensitivity_map[i].sensitivity;
> +}
> +
> +static int appleals_get_field_value_for_usage(struct hid_field *field,
> + unsigned int usage)
> +{
> + int u;
> +
> + if (!field)
> + return -1;
> +
> + for (u = 0; u < field->maxusage; u++) {
> + if (field->usage[u].hid == usage)
> + return u + field->logical_minimum;
> + }
> +
> + return -1;
> +}
> +
> +static __s32 appleals_get_field_value(struct appleals_device *als_dev,
> + struct hid_field *field)
> +{
> + hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_GET_REPORT);
> + hid_hw_wait(als_dev->hid_dev);
> +
> + return field->value[0];
> +}
> +
> +static void appleals_set_field_value(struct appleals_device *als_dev,
> + struct hid_field *field, __s32 value)
> +{
> + hid_set_field(field, 0, value);
> + hid_hw_request(als_dev->hid_dev, field->report, HID_REQ_SET_REPORT);
> +}
> +
> +static int appleals_get_config(struct appleals_device *als_dev,
> + unsigned int field_usage, __s32 *value)
> +{
> + struct hid_field *field;
> +
> + field = appleib_find_report_field(als_dev->cfg_report, field_usage);
> + if (!field)
> + return -EINVAL;
> +
> + *value = appleals_get_field_value(als_dev, field);
> +
> + return 0;
> +}
> +
> +static int appleals_set_config(struct appleals_device *als_dev,
> + unsigned int field_usage, __s32 value)
> +{
> + struct hid_field *field;
> +
> + field = appleib_find_report_field(als_dev->cfg_report, field_usage);
> + if (!field)
> + return -EINVAL;
> +
> + appleals_set_field_value(als_dev, field, value);
> +
> + return 0;
> +}
> +
> +static int appleals_set_enum_config(struct appleals_device *als_dev,
> + unsigned int field_usage,
> + unsigned int value_usage)
> +{
> + struct hid_field *field;
> + int value;
> +
> + field = appleib_find_report_field(als_dev->cfg_report, field_usage);
> + if (!field)
> + return -EINVAL;
> +
> + value = appleals_get_field_value_for_usage(field, value_usage);
can return -1, not checked
> +
> + appleals_set_field_value(als_dev, field, value);
> +
> + return 0;
> +}
> +
> +static void appleals_update_dyn_sensitivity(struct appleals_device *als_dev,
> + __s32 value)
> +{
> + int new_sens;
> + int rc;
> +
> + new_sens = appleals_compute_sensitivity(value,
> + als_dev->cur_sensitivity);
> + if (new_sens != als_dev->cur_sensitivity) {
> + rc = appleals_set_config(als_dev,
> + HID_USAGE_SENSOR_LIGHT_ILLUM |
> + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
> + new_sens);
> + if (!rc)
> + als_dev->cur_sensitivity = new_sens;
> + }
> +}
> +
> +static void appleals_push_new_value(struct appleals_device *als_dev,
> + __s32 value)
> +{
> + __s32 buf[2] = { value, value };
> +
> + iio_push_to_buffers(als_dev->iio_dev, buf);
> +
> + if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS)
> + appleals_update_dyn_sensitivity(als_dev, value);
> +}
> +
> +static int appleals_hid_event(struct hid_device *hdev, struct hid_field *field,
> + struct hid_usage *usage, __s32 value)
> +{
> + struct appleals_device *als_dev =
> + appleib_get_drvdata(hid_get_drvdata(hdev),
> + &appleals_hid_driver);
> + int rc = 0;
> +
> + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_SENSOR)
> + return 0;
> +
> + if (usage->hid == HID_USAGE_SENSOR_LIGHT_ILLUM) {
> + appleals_push_new_value(als_dev, value);
> + rc = 1;
> + }
> +
> + return rc;
> +}
> +
> +static int appleals_enable_events(struct iio_trigger *trig, bool enable)
> +{
> + struct appleals_device *als_dev = iio_trigger_get_drvdata(trig);
> + int value;
> +
> + /* set the sensor's reporting state */
> + appleals_set_enum_config(als_dev, HID_USAGE_SENSOR_PROP_REPORT_STATE,
> + enable ? HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
> + HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
> + als_dev->events_enabled = enable;
> +
> + /* if the sensor was enabled, push an initial value */
> + if (enable) {
> + value = appleals_get_field_value(als_dev, als_dev->illum_field);
> + appleals_push_new_value(als_dev, value);
> + }
> +
> + return 0;
> +}
> +
> +static int appleals_read_raw(struct iio_dev *iio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct appleals_device *als_dev =
> + *(struct appleals_device **)iio_priv(iio_dev);
> + __s32 value;
> + int rc;
> +
> + *val = 0;
> + *val2 = 0;
no need to set these here
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + case IIO_CHAN_INFO_PROCESSED:
> + *val = appleals_get_field_value(als_dev, als_dev->illum_field);
> + return IIO_VAL_INT;
> +
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + rc = appleals_get_config(als_dev,
> + HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
> + &value);
> + if (rc)
> + return rc;
> +
> + /* interval is in ms; val is in HZ, val2 in µHZ */
> + value = 1000000000 / value;
> + *val = value / 1000000;
> + *val2 = value - (*val * 1000000);
> +
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case IIO_CHAN_INFO_HYSTERESIS:
> + if (als_dev->cur_hysteresis == APPLEALS_DYN_SENS) {
> + *val = als_dev->cur_hysteresis;
> + return IIO_VAL_INT;
> + }
> +
> + rc = appleals_get_config(als_dev,
> + HID_USAGE_SENSOR_LIGHT_ILLUM |
> + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
> + val);
> + if (!rc) {
> + als_dev->cur_sensitivity = *val;
> + als_dev->cur_hysteresis = *val;
> + }
> + return rc ? rc : IIO_VAL_INT;
> +
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int appleals_write_raw(struct iio_dev *iio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + struct appleals_device *als_dev =
> + *(struct appleals_device **)iio_priv(iio_dev);
> + __s32 illum;
> + int rc;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + rc = appleals_set_config(als_dev,
> + HID_USAGE_SENSOR_PROP_REPORT_INTERVAL,
> + 1000000000 / (val * 1000000 + val2));
> + break;
maybe return directly instead of at the end (matter of taste);
here and in the other cases below
> +
> + case IIO_CHAN_INFO_HYSTERESIS:
> + if (val == APPLEALS_DYN_SENS) {
> + if (als_dev->cur_hysteresis != APPLEALS_DYN_SENS) {
> + als_dev->cur_hysteresis = val;
> + illum = appleals_get_field_value(als_dev,
> + als_dev->illum_field);
> + appleals_update_dyn_sensitivity(als_dev, illum);
> + }
> + rc = 0;
> + break;
> + }
> +
> + rc = appleals_set_config(als_dev,
> + HID_USAGE_SENSOR_LIGHT_ILLUM |
> + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS,
> + val);
> + if (!rc) {
> + als_dev->cur_sensitivity = val;
> + als_dev->cur_hysteresis = val;
> + }
> + break;
> +
> + default:
> + rc = -EINVAL;
> + }
> +
> + return rc;
> +}
> +
> +static const struct iio_chan_spec appleals_channels[] = {
> + {
> + .type = IIO_INTENSITY,
> + .modified = 1,
> + .channel2 = IIO_MOD_LIGHT_BOTH,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> + BIT(IIO_CHAN_INFO_RAW),
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> + BIT(IIO_CHAN_INFO_HYSTERESIS),
> + .scan_type = {
> + .sign = 'u',
> + .realbits = 32,
> + .storagebits = 32,
> + },
> + .scan_index = 0,
> + },
> + {
> + .type = IIO_LIGHT,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
> + BIT(IIO_CHAN_INFO_RAW),
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> + BIT(IIO_CHAN_INFO_HYSTERESIS),
> + .scan_type = {
> + .sign = 'u',
> + .realbits = 32,
> + .storagebits = 32,
> + },
> + .scan_index = 1,
> + }
> +};
> +
> +static const struct iio_trigger_ops appleals_trigger_ops = {
> + .set_trigger_state = &appleals_enable_events,
> +};
> +
> +static const struct iio_info appleals_info = {
> + .read_raw = &appleals_read_raw,
> + .write_raw = &appleals_write_raw,
> +};
> +
> +static void appleals_config_sensor(struct appleals_device *als_dev,
> + bool events_enabled, int sensitivity)
> +{
> + struct hid_field *field;
> + __s32 val;
> +
> + /*
> + * We're (often) in a probe here, so need to enable input processing
> + * in that case, but only in that case.
> + */
> + if (appleib_in_hid_probe(als_dev->ib_dev))
> + hid_device_io_start(als_dev->hid_dev);
> +
> + /* power on the sensor */
> + field = appleib_find_report_field(als_dev->cfg_report,
> + HID_USAGE_SENSOR_PROY_POWER_STATE);
> + val = appleals_get_field_value_for_usage(field,
> + HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM);
what if -1?
> + hid_set_field(field, 0, val);
> +
> + /* configure reporting of change events */
> + field = appleib_find_report_field(als_dev->cfg_report,
> + HID_USAGE_SENSOR_PROP_REPORT_STATE);
> + val = appleals_get_field_value_for_usage(field,
> + events_enabled ?
> + HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM :
> + HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM);
> + hid_set_field(field, 0, val);
> +
> + /* report change events asap */
> + field = appleib_find_report_field(als_dev->cfg_report,
> + HID_USAGE_SENSOR_PROP_REPORT_INTERVAL);
> + hid_set_field(field, 0, field->logical_minimum);
> +
> + /*
> + * Set initial change sensitivity; if dynamic, enabling trigger will set
> + * it instead.
> + */
> + if (sensitivity != APPLEALS_DYN_SENS) {
> + field = appleib_find_report_field(als_dev->cfg_report,
> + HID_USAGE_SENSOR_LIGHT_ILLUM |
> + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS);
> +
> + hid_set_field(field, 0, sensitivity);
> + }
> +
> + /* write the new config to the sensor */
> + hid_hw_request(als_dev->hid_dev, als_dev->cfg_report,
> + HID_REQ_SET_REPORT);
> +
> + if (appleib_in_hid_probe(als_dev->ib_dev))
> + hid_device_io_stop(als_dev->hid_dev);
> +};
no semicolon at the end of a function please
> +
> +static int appleals_config_iio(struct appleals_device *als_dev)
> +{
> + struct iio_dev *iio_dev;
> + struct iio_trigger *iio_trig;
> + int rc;
> +
> + /* create and register iio device */
> + iio_dev = iio_device_alloc(sizeof(als_dev));
how about using the devm_ variants?
> + if (!iio_dev)
> + return -ENOMEM;
> +
> + *(struct appleals_device **)iio_priv(iio_dev) = als_dev;
> +
> + iio_dev->channels = appleals_channels;
> + iio_dev->num_channels = ARRAY_SIZE(appleals_channels);
> + iio_dev->dev.parent = &als_dev->hid_dev->dev;
> + iio_dev->info = &appleals_info;
> + iio_dev->name = "als";
> + iio_dev->modes = INDIO_DIRECT_MODE;
> +
> + rc = iio_triggered_buffer_setup(iio_dev, &iio_pollfunc_store_time, NULL,
> + NULL);
> + if (rc) {
> + dev_err(als_dev->log_dev, "failed to set up iio triggers: %d\n",
just one trigger?
> + rc);
> + goto free_iio_dev;
> + }
> +
> + iio_trig = iio_trigger_alloc("%s-dev%d", iio_dev->name, iio_dev->id);
> + if (!iio_trig) {
> + rc = -ENOMEM;
> + goto clean_trig_buf;
> + }
> +
> + iio_trig->dev.parent = &als_dev->hid_dev->dev;
> + iio_trig->ops = &appleals_trigger_ops;
> + iio_trigger_set_drvdata(iio_trig, als_dev);
> +
> + rc = iio_trigger_register(iio_trig);
> + if (rc) {
> + dev_err(als_dev->log_dev, "failed to register iio trigger: %d\n",
some messages start lowercase, some uppercase (nitpicking)
> + rc);
> + goto free_iio_trig;
> + }
> +
> + als_dev->iio_trig = iio_trig;
> +
> + rc = iio_device_register(iio_dev);
> + if (rc) {
> + dev_err(als_dev->log_dev, "failed to register iio device: %d\n",
> + rc);
> + goto unreg_iio_trig;
> + }
> +
> + als_dev->iio_dev = iio_dev;
> +
> + return 0;
> +
> +unreg_iio_trig:
> + iio_trigger_unregister(iio_trig);
> +free_iio_trig:
> + iio_trigger_free(iio_trig);
> + als_dev->iio_trig = NULL;
> +clean_trig_buf:
> + iio_triggered_buffer_cleanup(iio_dev);
> +free_iio_dev:
> + iio_device_free(iio_dev);
> +
> + return rc;
> +}
> +
> +static int appleals_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + struct appleals_device *als_dev =
> + appleib_get_drvdata(hid_get_drvdata(hdev),
> + &appleals_hid_driver);
> + struct hid_field *state_field;
> + struct hid_field *illum_field;
> + int rc;
> +
> + /* find als fields and reports */
> + state_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
> + HID_USAGE_SENSOR_PROP_REPORT_STATE);
> + illum_field = appleib_find_hid_field(hdev, HID_USAGE_SENSOR_ALS,
> + HID_USAGE_SENSOR_LIGHT_ILLUM);
> + if (!state_field || !illum_field)
> + return -ENODEV;
> +
> + if (als_dev->hid_dev) {
> + dev_warn(als_dev->log_dev,
> + "Found duplicate ambient light sensor - ignoring\n");
> + return -EBUSY;
> + }
> +
> + dev_info(als_dev->log_dev, "Found ambient light sensor\n");
in general avoid logging for the OK case, it just clutters the log
> +
> + /* initialize device */
> + als_dev->hid_dev = hdev;
> + als_dev->cfg_report = state_field->report;
> + als_dev->illum_field = illum_field;
> +
> + als_dev->cur_hysteresis = APPLEALS_DEF_CHANGE_SENS;
> + als_dev->cur_sensitivity = APPLEALS_DEF_CHANGE_SENS;
> + appleals_config_sensor(als_dev, false, als_dev->cur_sensitivity);
> +
> + rc = appleals_config_iio(als_dev);
> + if (rc)
> + return rc;
> +
> + return 0;
> +}
> +
> +static void appleals_remove(struct hid_device *hdev)
> +{
> + struct appleals_device *als_dev =
> + appleib_get_drvdata(hid_get_drvdata(hdev),
> + &appleals_hid_driver);
> +
could be a lot less if devm_ were used?
> + if (als_dev->iio_dev) {
> + iio_device_unregister(als_dev->iio_dev);
> +
> + iio_trigger_unregister(als_dev->iio_trig);
> + iio_trigger_free(als_dev->iio_trig);
> + als_dev->iio_trig = NULL;
> +
> + iio_triggered_buffer_cleanup(als_dev->iio_dev);
> + iio_device_free(als_dev->iio_dev);
> + als_dev->iio_dev = NULL;
> + }
> +
> + als_dev->hid_dev = NULL;
> +}
> +
> +#ifdef CONFIG_PM
> +static int appleals_reset_resume(struct hid_device *hdev)
> +{
> + struct appleals_device *als_dev =
> + appleib_get_drvdata(hid_get_drvdata(hdev),
> + &appleals_hid_driver);
> +
> + appleals_config_sensor(als_dev, als_dev->events_enabled,
> + als_dev->cur_sensitivity);
> +
> + return 0;
> +}
> +#endif
> +
> +static struct hid_driver appleals_hid_driver = {
> + .name = "apple-ib-als",
> + .probe = appleals_probe,
> + .remove = appleals_remove,
> + .event = appleals_hid_event,
> +#ifdef CONFIG_PM
> + .reset_resume = appleals_reset_resume,
> +#endif
> +};
> +
> +static int appleals_platform_probe(struct platform_device *pdev)
> +{
> + struct appleib_platform_data *pdata = pdev->dev.platform_data;
> + struct appleib_device *ib_dev = pdata->ib_dev;
> + struct appleals_device *als_dev;
> + int rc;
> +
> + als_dev = kzalloc(sizeof(*als_dev), GFP_KERNEL);
> + if (!als_dev)
> + return -ENOMEM;
> +
> + als_dev->ib_dev = ib_dev;
> + als_dev->log_dev = pdata->log_dev;
> +
> + rc = appleib_register_hid_driver(ib_dev, &appleals_hid_driver, als_dev);
> + if (rc) {
> + dev_err(als_dev->log_dev, "Error registering hid driver: %d\n",
> + rc);
> + goto error;
> + }
> +
> + platform_set_drvdata(pdev, als_dev);
> +
> + return 0;
> +
> +error:
> + kfree(als_dev);
> + return rc;
> +}
> +
> +static int appleals_platform_remove(struct platform_device *pdev)
> +{
> + struct appleib_platform_data *pdata = pdev->dev.platform_data;
> + struct appleib_device *ib_dev = pdata->ib_dev;
> + struct appleals_device *als_dev = platform_get_drvdata(pdev);
> + int rc;
> +
> + rc = appleib_unregister_hid_driver(ib_dev, &appleals_hid_driver);
> + if (rc) {
> + dev_err(als_dev->log_dev,
> + "Error unregistering hid driver: %d\n", rc);
> + goto error;
> + }
> +
> + kfree(als_dev);
> +
> + return 0;
> +
> +error:
> + return rc;
> +}
> +
> +static const struct platform_device_id appleals_platform_ids[] = {
> + { .name = PLAT_NAME_IB_ALS },
> + { }
> +};
> +MODULE_DEVICE_TABLE(platform, appleals_platform_ids);
> +
> +static struct platform_driver appleals_platform_driver = {
> + .id_table = appleals_platform_ids,
> + .driver = {
> + .name = "apple-ib-als",
> + },
> + .probe = appleals_platform_probe,
> + .remove = appleals_platform_remove,
> +};
> +
> +module_platform_driver(appleals_platform_driver);
> +
> +MODULE_AUTHOR("Ronald Tschalär");
> +MODULE_DESCRIPTION("Apple iBridge ALS driver");
> +MODULE_LICENSE("GPL v2");
>
--
Peter Meerwald-Stadler
Mobile: +43 664 24 44 418
^ permalink raw reply
* Re: [PATCH 1/3] mfd: apple-ibridge: Add Apple iBridge MFD driver.
From: Jonathan Cameron @ 2019-04-22 11:34 UTC (permalink / raw)
To: Ronald Tschalär
Cc: Jiri Kosina, Benjamin Tissoires, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Lee Jones,
linux-input, linux-iio, linux-kernel
In-Reply-To: <20190422031251.11968-2-ronald@innovation.ch>
On Sun, 21 Apr 2019 20:12:49 -0700
Ronald Tschalär <ronald@innovation.ch> wrote:
> The iBridge device provides access to several devices, including:
> - the Touch Bar
> - the iSight webcam
> - the light sensor
> - the fingerprint sensor
>
> This driver provides the core support for managing the iBridge device
> and the access to the underlying devices. In particular, since the
> functionality for the touch bar and light sensor is exposed via USB HID
> interfaces, and the same HID device is used for multiple functions, this
> driver provides a multiplexing layer that allows multiple HID drivers to
> be registered for a given HID device. This allows the touch bar and ALS
> driver to be separated out into their own modules.
>
> Signed-off-by: Ronald Tschalär <ronald@innovation.ch
Hi Ronald,
I've only taken a fairly superficial look at this. A few global
things to note though.
1. Please either use kernel-doc style for function descriptions, or
do not. Right now you are sort of half way there.
2. There is quite a complex nest of separate structures being allocated,
so think about whether they can be simplified. In particular
use of container_of macros can allow a lot of forwards and backwards
pointers to be dropped if you embed the various structures directly.
This obviously needs hid and mfd review though as neither is my
area of expertise!
Jonathan
>
> ---
> drivers/mfd/Kconfig | 15 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/apple-ibridge.c | 883 ++++++++++++++++++++++++++++++
> include/linux/mfd/apple-ibridge.h | 39 ++
> 4 files changed, 938 insertions(+)
> create mode 100644 drivers/mfd/apple-ibridge.c
> create mode 100644 include/linux/mfd/apple-ibridge.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 76f9909cf396..d55fa77faacf 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1916,5 +1916,20 @@ config RAVE_SP_CORE
> Select this to get support for the Supervisory Processor
> device found on several devices in RAVE line of hardware.
>
> +config MFD_APPLE_IBRIDGE
> + tristate "Apple iBridge chip"
> + depends on ACPI
> + depends on USB_HID
> + depends on X86 || COMPILE_TEST
> + select MFD_CORE
> + help
> + This MFD provides the core support for the Apple iBridge chip
> + found on recent MacBookPro's. The drivers for the Touch Bar
> + (apple-ib-tb) and light sensor (apple-ib-als) need to be
> + enabled separately.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called apple-ibridge.
> +
> endmenu
> endif
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 12980a4ad460..c364e0e9d313 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -241,4 +241,5 @@ obj-$(CONFIG_MFD_MXS_LRADC) += mxs-lradc.o
> obj-$(CONFIG_MFD_SC27XX_PMIC) += sprd-sc27xx-spi.o
> obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o
> obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
> +obj-$(CONFIG_MFD_APPLE_IBRIDGE) += apple-ibridge.o
>
> diff --git a/drivers/mfd/apple-ibridge.c b/drivers/mfd/apple-ibridge.c
> new file mode 100644
> index 000000000000..56d325396961
> --- /dev/null
> +++ b/drivers/mfd/apple-ibridge.c
> @@ -0,0 +1,883 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +/**
> + * MacBookPro models with a Touch Bar (13,[23] and 14,[23]) have an Apple
> + * iBridge chip (also known as T1 chip) which exposes the touch bar,
> + * built-in webcam (iSight), ambient light sensor, and Secure Enclave
> + * Processor (SEP) for TouchID. It shows up in the system as a USB device
> + * with 3 configurations: 'Default iBridge Interfaces', 'Default iBridge
> + * Interfaces(OS X)', and 'Default iBridge Interfaces(Recovery)'. While
> + * the second one is used by MacOS to provide the fancy touch bar
> + * functionality with custom buttons etc, this driver just uses the first.
> + *
> + * In the first (default after boot) configuration, 4 usb interfaces are
> + * exposed: 2 related to the webcam, and 2 USB HID interfaces representing
> + * the touch bar and the ambient light sensor (and possibly the SEP,
> + * though at this point in time nothing is known about that). The webcam
> + * interfaces are already handled by the uvcvideo driver; furthermore, the
> + * handling of the input reports when "keys" on the touch bar are pressed
> + * is already handled properly by the generic USB HID core. This leaves
> + * the management of the touch bar modes (e.g. switching between function
> + * and special keys when the FN key is pressed), the touch bar display
> + * (dimming and turning off), the key-remapping when the FN key is
> + * pressed, and handling of the light sensor.
> + *
> + * This driver is implemented as an MFD driver, with the touch bar and ALS
> + * functions implemented by appropriate subdrivers (mfd cells). Because
> + * both those are basically hid drivers, but the current kernel driver
> + * structure does not allow more than one driver per device, this driver
> + * implements a demuxer for hid drivers: it registers itself as a hid
> + * driver with the core, and in turn it lets the subdrivers register
> + * themselves as hid drivers with this driver; the callbacks from the core
> + * are then forwarded to the subdrivers.
> + *
> + * Lastly, this driver also takes care of the power-management for the
> + * iBridge when suspending and resuming.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +#include <linux/list.h>
> +#include <linux/mfd/apple-ibridge.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/rculist.h>
> +#include <linux/slab.h>
> +#include <linux/srcu.h>
> +#include <linux/usb.h>
> +
> +#include "../hid/usbhid/usbhid.h"
> +
> +#define USB_ID_VENDOR_APPLE 0x05ac
> +#define USB_ID_PRODUCT_IBRIDGE 0x8600
> +
> +#define APPLETB_BASIC_CONFIG 1
> +
> +#define LOG_DEV(ib_dev) (&(ib_dev)->acpi_dev->dev)
> +
> +struct appleib_device {
> + struct acpi_device *acpi_dev;
> + acpi_handle asoc_socw;
> + struct list_head hid_drivers;
> + struct list_head hid_devices;
> + struct mfd_cell *subdevs;
> + struct mutex update_lock; /* protect updates to all lists */
> + struct srcu_struct lists_srcu;
> + bool in_hid_probe;
> +};
> +
> +struct appleib_hid_drv_info {
> + struct list_head entry;
> + struct hid_driver *driver;
> + void *driver_data;
> +};
> +
> +struct appleib_hid_dev_info {
> + struct list_head entry;
> + struct list_head drivers;
> + struct hid_device *device;
> + const struct hid_device_id *device_id;
> + bool started;
> +};
> +
> +static const struct mfd_cell appleib_subdevs[] = {
> + { .name = PLAT_NAME_IB_TB },
> + { .name = PLAT_NAME_IB_ALS },
> +};
> +
> +static struct appleib_device *appleib_dev;
> +
> +#define call_void_driver_func(drv_info, fn, ...) \
This sort of macro may seem like a good idea because it saves a few lines
of code. However, that comes at the cost of readability, so just
put the code inline.
> + do { \
> + if ((drv_info)->driver->fn) \
> + (drv_info)->driver->fn(__VA_ARGS__); \
> + } while (0)
> +
> +#define call_driver_func(drv_info, fn, ret_type, ...) \
> + ({ \
> + ret_type rc = 0; \
> + \
> + if ((drv_info)->driver->fn) \
> + rc = (drv_info)->driver->fn(__VA_ARGS__); \
> + \
> + rc; \
> + })
> +
> +static void appleib_remove_driver(struct appleib_device *ib_dev,
> + struct appleib_hid_drv_info *drv_info,
> + struct appleib_hid_dev_info *dev_info)
> +{
> + list_del_rcu(&drv_info->entry);
> + synchronize_srcu(&ib_dev->lists_srcu);
> +
> + call_void_driver_func(drv_info, remove, dev_info->device);
> +
> + kfree(drv_info);
> +}
> +
> +static int appleib_probe_driver(struct appleib_hid_drv_info *drv_info,
> + struct appleib_hid_dev_info *dev_info)
> +{
> + struct appleib_hid_drv_info *d;
> + int rc = 0;
> +
> + rc = call_driver_func(drv_info, probe, int, dev_info->device,
> + dev_info->device_id);
> + if (rc)
> + return rc;
> +
> + d = kmemdup(drv_info, sizeof(*drv_info), GFP_KERNEL);
> + if (!d) {
> + call_void_driver_func(drv_info, remove, dev_info->device);
> + return -ENOMEM;
> + }
> +
> + list_add_tail_rcu(&d->entry, &dev_info->drivers);
> + return 0;
> +}
> +
> +static void appleib_remove_driver_attachments(struct appleib_device *ib_dev,
> + struct appleib_hid_dev_info *dev_info,
> + struct hid_driver *driver)
> +{
> + struct appleib_hid_drv_info *drv_info;
> + struct appleib_hid_drv_info *tmp;
> +
> + list_for_each_entry_safe(drv_info, tmp, &dev_info->drivers, entry) {
> + if (!driver || drv_info->driver == driver)
> + appleib_remove_driver(ib_dev, drv_info, dev_info);
> + }
> +}
> +
> +/*
> + * Find all devices that are attached to this driver and detach them.
> + *
> + * Note: this must be run with update_lock held.
> + */
> +static void appleib_detach_devices(struct appleib_device *ib_dev,
> + struct hid_driver *driver)
> +{
> + struct appleib_hid_dev_info *dev_info;
> +
> + list_for_each_entry(dev_info, &ib_dev->hid_devices, entry)
> + appleib_remove_driver_attachments(ib_dev, dev_info, driver);
> +}
> +
> +/*
> + * Find all drivers that are attached to this device and detach them.
> + *
> + * Note: this must be run with update_lock held.
> + */
> +static void appleib_detach_drivers(struct appleib_device *ib_dev,
> + struct appleib_hid_dev_info *dev_info)
> +{
> + appleib_remove_driver_attachments(ib_dev, dev_info, NULL);
> +}
> +
> +/**
> + * Unregister a previously registered HID driver from us.
> + * @ib_dev: the appleib_device from which to unregister the driver
> + * @driver: the driver to unregister
> + */
> +int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
> + struct hid_driver *driver)
> +{
> + struct appleib_hid_drv_info *drv_info;
> +
> + mutex_lock(&ib_dev->update_lock);
> +
> + list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
> + if (drv_info->driver == driver) {
This block does look like it perhaps should be in helper function?
Would help with readability.
> + appleib_detach_devices(ib_dev, driver);
> + list_del_rcu(&drv_info->entry);
> + mutex_unlock(&ib_dev->update_lock);
> + synchronize_srcu(&ib_dev->lists_srcu);
> + kfree(drv_info);
> + dev_info(LOG_DEV(ib_dev), "unregistered driver '%s'\n",
> + driver->name);
> + return 0;
> + }
> + }
> +
> + mutex_unlock(&ib_dev->update_lock);
> +
> + return -ENOENT;
> +}
> +EXPORT_SYMBOL_GPL(appleib_unregister_hid_driver);
> +
> +static int appleib_start_hid_events(struct appleib_hid_dev_info *dev_info)
> +{
> + struct hid_device *hdev = dev_info->device;
> + int rc;
> +
> + rc = hid_connect(hdev, HID_CONNECT_DEFAULT);
> + if (rc) {
> + hid_err(hdev, "ib: hid connect failed (%d)\n", rc);
> + return rc;
> + }
> +
> + rc = hid_hw_open(hdev);
> + if (rc) {
> + hid_err(hdev, "ib: failed to open hid: %d\n", rc);
> + hid_disconnect(hdev);
> + }
> +
> + if (!rc)
> + dev_info->started = true;
> +
> + return rc;
> +}
> +
> +static void appleib_stop_hid_events(struct appleib_hid_dev_info *dev_info)
> +{
> + if (dev_info->started) {
> + hid_hw_close(dev_info->device);
> + hid_disconnect(dev_info->device);
> + dev_info->started = false;
> + }
> +}
> +
> +/**
> + * Register a HID driver with us.
> + * @ib_dev: the appleib_device with which to register the driver
> + * @driver: the driver to register
> + * @data: the driver-data to associate with the driver; this is available
> + * from appleib_get_drvdata(...).
> + */
> +int appleib_register_hid_driver(struct appleib_device *ib_dev,
> + struct hid_driver *driver, void *data)
> +{
> + struct appleib_hid_drv_info *drv_info;
> + struct appleib_hid_dev_info *dev_info;
> + int rc;
> +
> + if (!driver->probe)
> + return -EINVAL;
> +
> + drv_info = kzalloc(sizeof(*drv_info), GFP_KERNEL);
> + if (!drv_info)
> + return -ENOMEM;
> +
> + drv_info->driver = driver;
> + drv_info->driver_data = data;
> +
> + mutex_lock(&ib_dev->update_lock);
> +
> + list_add_tail_rcu(&drv_info->entry, &ib_dev->hid_drivers);
> +
> + list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
> + appleib_stop_hid_events(dev_info);
> +
> + appleib_probe_driver(drv_info, dev_info);
> +
> + rc = appleib_start_hid_events(dev_info);
> + if (rc)
> + appleib_detach_drivers(ib_dev, dev_info);
> + }
> +
> + mutex_unlock(&ib_dev->update_lock);
> +
> + dev_info(LOG_DEV(ib_dev), "registered driver '%s'\n", driver->name);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(appleib_register_hid_driver);
> +
> +/**
> + * Get the driver-specific data associated with the given, previously
> + * registered HID driver (provided in the appleib_register_hid_driver()
> + * call).
> + * @ib_dev: the appleib_device with which the driver was registered
> + * @driver: the driver for which to get the data
> + */
> +void *appleib_get_drvdata(struct appleib_device *ib_dev,
> + struct hid_driver *driver)
> +{
> + struct appleib_hid_drv_info *drv_info;
> + void *drv_data = NULL;
> + int idx;
> +
> + idx = srcu_read_lock(&ib_dev->lists_srcu);
> +
> + list_for_each_entry_rcu(drv_info, &ib_dev->hid_drivers, entry) {
> + if (drv_info->driver == driver) {
> + drv_data = drv_info->driver_data;
> + break;
> + }
> + }
> +
> + srcu_read_unlock(&ib_dev->lists_srcu, idx);
> +
> + return drv_data;
> +}
> +EXPORT_SYMBOL_GPL(appleib_get_drvdata);
> +
> +/*
> + * Forward a hid-driver callback to all registered sub-drivers. This is for
> + * callbacks that return a status as an int.
> + * @hdev the hid-device
> + * @forward a function that calls the callback on the given driver
> + * @args arguments for the forward function
> + */
> +static int appleib_forward_int_op(struct hid_device *hdev,
> + int (*forward)(struct appleib_hid_drv_info *,
> + struct hid_device *, void *),
> + void *args)
> +{
> + struct appleib_device *ib_dev = hid_get_drvdata(hdev);
> + struct appleib_hid_dev_info *dev_info;
> + struct appleib_hid_drv_info *drv_info;
> + int idx;
> + int rc = 0;
> +
> + idx = srcu_read_lock(&ib_dev->lists_srcu);
> +
> + list_for_each_entry_rcu(dev_info, &ib_dev->hid_devices, entry) {
> + if (dev_info->device != hdev)
> + continue;
> +
> + list_for_each_entry_rcu(drv_info, &dev_info->drivers, entry) {
> + rc = forward(drv_info, hdev, args);
> + if (rc)
> + break;
> + }
> +
> + break;
> + }
> +
> + srcu_read_unlock(&ib_dev->lists_srcu, idx);
> +
> + return rc;
> +}
> +
> +struct appleib_hid_event_args {
> + struct hid_field *field;
> + struct hid_usage *usage;
> + __s32 value;
> +};
> +
> +static int appleib_hid_event_fwd(struct appleib_hid_drv_info *drv_info,
> + struct hid_device *hdev, void *args)
> +{
> + struct appleib_hid_event_args *evt_args = args;
> +
> + return call_driver_func(drv_info, event, int, hdev, evt_args->field,
> + evt_args->usage, evt_args->value);
> +}
> +
> +static int appleib_hid_event(struct hid_device *hdev, struct hid_field *field,
> + struct hid_usage *usage, __s32 value)
> +{
> + struct appleib_hid_event_args args = {
> + .field = field,
> + .usage = usage,
> + .value = value,
> + };
> +
> + return appleib_forward_int_op(hdev, appleib_hid_event_fwd, &args);
> +}
> +
> +static __u8 *appleib_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> + unsigned int *rsize)
> +{
> + /* Some fields have a size of 64 bits, which according to HID 1.11
> + * Section 8.4 is not valid ("An item field cannot span more than 4
> + * bytes in a report"). Furthermore, hid_field_extract() complains
> + * when encountering such a field. So turn them into two 32-bit fields
> + * instead.
> + */
> +
> + if (*rsize == 634 &&
> + /* Usage Page 0xff12 (vendor defined) */
> + rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
> + /* Usage 0x51 */
> + rdesc[416] == 0x09 && rdesc[417] == 0x51 &&
> + /* report size 64 */
> + rdesc[432] == 0x75 && rdesc[433] == 64 &&
> + /* report count 1 */
> + rdesc[434] == 0x95 && rdesc[435] == 1) {
> + rdesc[433] = 32;
> + rdesc[435] = 2;
> + hid_dbg(hdev, "Fixed up first 64-bit field\n");
> + }
> +
> + if (*rsize == 634 &&
> + /* Usage Page 0xff12 (vendor defined) */
> + rdesc[212] == 0x06 && rdesc[213] == 0x12 && rdesc[214] == 0xff &&
> + /* Usage 0x51 */
> + rdesc[611] == 0x09 && rdesc[612] == 0x51 &&
> + /* report size 64 */
> + rdesc[627] == 0x75 && rdesc[628] == 64 &&
> + /* report count 1 */
> + rdesc[629] == 0x95 && rdesc[630] == 1) {
> + rdesc[628] = 32;
> + rdesc[630] = 2;
> + hid_dbg(hdev, "Fixed up second 64-bit field\n");
> + }
> +
> + return rdesc;
> +}
> +
> +static int appleib_input_configured_fwd(struct appleib_hid_drv_info *drv_info,
> + struct hid_device *hdev, void *args)
> +{
> + return call_driver_func(drv_info, input_configured, int, hdev,
> + (struct hid_input *)args);
> +}
> +
> +static int appleib_input_configured(struct hid_device *hdev,
> + struct hid_input *hidinput)
> +{
> + return appleib_forward_int_op(hdev, appleib_input_configured_fwd,
> + hidinput);
> +}
> +
> +#ifdef CONFIG_PM
> +static int appleib_hid_suspend_fwd(struct appleib_hid_drv_info *drv_info,
> + struct hid_device *hdev, void *args)
> +{
> + return call_driver_func(drv_info, suspend, int, hdev,
> + *(pm_message_t *)args);
> +}
> +
> +static int appleib_hid_suspend(struct hid_device *hdev, pm_message_t message)
> +{
> + return appleib_forward_int_op(hdev, appleib_hid_suspend_fwd, &message);
> +}
> +
> +static int appleib_hid_resume_fwd(struct appleib_hid_drv_info *drv_info,
> + struct hid_device *hdev, void *args)
> +{
> + return call_driver_func(drv_info, resume, int, hdev);
> +}
> +
> +static int appleib_hid_resume(struct hid_device *hdev)
> +{
> + return appleib_forward_int_op(hdev, appleib_hid_resume_fwd, NULL);
> +}
> +
> +static int appleib_hid_reset_resume_fwd(struct appleib_hid_drv_info *drv_info,
> + struct hid_device *hdev, void *args)
> +{
> + return call_driver_func(drv_info, reset_resume, int, hdev);
> +}
> +
> +static int appleib_hid_reset_resume(struct hid_device *hdev)
> +{
> + return appleib_forward_int_op(hdev, appleib_hid_reset_resume_fwd, NULL);
> +}
> +#endif /* CONFIG_PM */
> +
> +/**
> + * Find the field in the report with the given usage.
> + * @report: the report to search
> + * @field_usage: the usage of the field to search for
> + */
> +struct hid_field *appleib_find_report_field(struct hid_report *report,
> + unsigned int field_usage)
> +{
> + int f, u;
> +
> + for (f = 0; f < report->maxfield; f++) {
> + struct hid_field *field = report->field[f];
> +
> + if (field->logical == field_usage)
> + return field;
> +
> + for (u = 0; u < field->maxusage; u++) {
> + if (field->usage[u].hid == field_usage)
> + return field;
> + }
> + }
> +
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(appleib_find_report_field);
> +
> +/**
Please use correct kernel-doc style rather than parts of it.
> + * Search all the reports of the device for the field with the given usage.
> + * @hdev: the device whose reports to search
> + * @application: the usage of application collection that the field must
> + * belong to
> + * @field_usage: the usage of the field to search for
> + */
> +struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
> + unsigned int application,
> + unsigned int field_usage)
> +{
> + static const int report_types[] = { HID_INPUT_REPORT, HID_OUTPUT_REPORT,
> + HID_FEATURE_REPORT };
> + struct hid_report *report;
> + struct hid_field *field;
> + int t;
> +
> + for (t = 0; t < ARRAY_SIZE(report_types); t++) {
> + struct list_head *report_list =
> + &hdev->report_enum[report_types[t]].report_list;
> + list_for_each_entry(report, report_list, list) {
> + if (report->application != application)
> + continue;
> +
> + field = appleib_find_report_field(report, field_usage);
> + if (field)
> + return field;
> + }
> + }
> +
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(appleib_find_hid_field);
> +
> +/**
> + * Return whether we're currently inside a hid_device_probe or not.
> + * @ib_dev: the appleib_device
> + */
> +bool appleib_in_hid_probe(struct appleib_device *ib_dev)
> +{
> + return ib_dev->in_hid_probe;
> +}
> +EXPORT_SYMBOL_GPL(appleib_in_hid_probe);
> +
> +static struct appleib_hid_dev_info *
> +appleib_add_device(struct appleib_device *ib_dev, struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + struct appleib_hid_dev_info *dev_info;
> + struct appleib_hid_drv_info *drv_info;
> +
> + /* allocate device-info for this device */
> + dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
> + if (!dev_info)
> + return NULL;
> +
> + INIT_LIST_HEAD(&dev_info->drivers);
> + dev_info->device = hdev;
> + dev_info->device_id = id;
> +
> + /* notify all our sub drivers */
> + mutex_lock(&ib_dev->update_lock);
> +
This is interesting. I'd like to see a comment here on what
this flag is going to do.
> + ib_dev->in_hid_probe = true;
> +
> + list_for_each_entry(drv_info, &ib_dev->hid_drivers, entry) {
> + appleib_probe_driver(drv_info, dev_info);
> + }
> +
> + ib_dev->in_hid_probe = false;
> +
> + /* remember this new device */
> + list_add_tail_rcu(&dev_info->entry, &ib_dev->hid_devices);
> +
> + mutex_unlock(&ib_dev->update_lock);
> +
> + return dev_info;
> +}
> +
> +static void appleib_remove_device(struct appleib_device *ib_dev,
> + struct appleib_hid_dev_info *dev_info)
> +{
> + list_del_rcu(&dev_info->entry);
> + synchronize_srcu(&ib_dev->lists_srcu);
> +
> + appleib_detach_drivers(ib_dev, dev_info);
> +
> + kfree(dev_info);
> +}
> +
> +static int appleib_hid_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + struct appleib_device *ib_dev;
> + struct appleib_hid_dev_info *dev_info;
> + struct usb_device *udev;
> + int rc;
> +
> + /* check usb config first */
> + udev = hid_to_usb_dev(hdev);
> +
> + if (udev->actconfig->desc.bConfigurationValue != APPLETB_BASIC_CONFIG) {
> + rc = usb_driver_set_configuration(udev, APPLETB_BASIC_CONFIG);
> + return rc ? rc : -ENODEV;
> + }
> +
> + /* Assign the driver data */
> + ib_dev = appleib_dev;
> + hid_set_drvdata(hdev, ib_dev);
> +
> + /* initialize the report info */
> + rc = hid_parse(hdev);
> + if (rc) {
> + hid_err(hdev, "ib: hid parse failed (%d)\n", rc);
> + goto error;
> + }
> +
> + /* alloc bufs etc so probe's can send requests; but connect later */
> + rc = hid_hw_start(hdev, 0);
> + if (rc) {
> + hid_err(hdev, "ib: hw start failed (%d)\n", rc);
> + goto error;
> + }
> +
> + /* add this hdev to our device list */
> + dev_info = appleib_add_device(ib_dev, hdev, id);
> + if (!dev_info) {
> + rc = -ENOMEM;
> + goto stop_hw;
> + }
> +
> + /* start the hid */
> + rc = appleib_start_hid_events(dev_info);
> + if (rc)
> + goto remove_dev;
> +
> + return 0;
> +
> +remove_dev:
> + mutex_lock(&ib_dev->update_lock);
> + appleib_remove_device(ib_dev, dev_info);
> + mutex_unlock(&ib_dev->update_lock);
> +stop_hw:
> + hid_hw_stop(hdev);
> +error:
> + return rc;
> +}
> +
> +static void appleib_hid_remove(struct hid_device *hdev)
> +{
> + struct appleib_device *ib_dev = hid_get_drvdata(hdev);
> + struct appleib_hid_dev_info *dev_info;
> +
> + mutex_lock(&ib_dev->update_lock);
> +
> + list_for_each_entry(dev_info, &ib_dev->hid_devices, entry) {
> + if (dev_info->device == hdev) {
> + appleib_stop_hid_events(dev_info);
> + appleib_remove_device(ib_dev, dev_info);
> + break;
> + }
> + }
> +
> + mutex_unlock(&ib_dev->update_lock);
> +
> + hid_hw_stop(hdev);
> +}
> +
> +static const struct hid_device_id appleib_hid_devices[] = {
> + { HID_USB_DEVICE(USB_ID_VENDOR_APPLE, USB_ID_PRODUCT_IBRIDGE) },
> + { },
> +};
> +
> +static struct hid_driver appleib_hid_driver = {
> + .name = "apple-ibridge-hid",
> + .id_table = appleib_hid_devices,
> + .probe = appleib_hid_probe,
> + .remove = appleib_hid_remove,
> + .event = appleib_hid_event,
> + .report_fixup = appleib_report_fixup,
> + .input_configured = appleib_input_configured,
> +#ifdef CONFIG_PM
> + .suspend = appleib_hid_suspend,
> + .resume = appleib_hid_resume,
> + .reset_resume = appleib_hid_reset_resume,
> +#endif
> +};
> +
> +static struct appleib_device *appleib_alloc_device(struct acpi_device *acpi_dev)
> +{
> + struct appleib_device *ib_dev;
> + acpi_status sts;
> + int rc;
> +
> + /* allocate */
Drop comments that don't anything a quick glance at the code would tell you.
> + ib_dev = kzalloc(sizeof(*ib_dev), GFP_KERNEL);
> + if (!ib_dev)
> + return ERR_PTR(-ENOMEM);
> +
> + /* init structures */
> + INIT_LIST_HEAD(&ib_dev->hid_drivers);
> + INIT_LIST_HEAD(&ib_dev->hid_devices);
> + mutex_init(&ib_dev->update_lock);
> + init_srcu_struct(&ib_dev->lists_srcu);
> +
> + ib_dev->acpi_dev = acpi_dev;
> +
> + /* get iBridge acpi power control method */
> + sts = acpi_get_handle(acpi_dev->handle, "SOCW", &ib_dev->asoc_socw);
> + if (ACPI_FAILURE(sts)) {
> + dev_err(LOG_DEV(ib_dev),
> + "Error getting handle for ASOC.SOCW method: %s\n",
> + acpi_format_exception(sts));
> + rc = -ENXIO;
> + goto free_mem;
> + }
> +
> + /* ensure iBridge is powered on */
> + sts = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
> + if (ACPI_FAILURE(sts))
> + dev_warn(LOG_DEV(ib_dev), "SOCW(1) failed: %s\n",
> + acpi_format_exception(sts));
> +
> + return ib_dev;
> +
> +free_mem:
> + kfree(ib_dev);
> + return ERR_PTR(rc);
> +}
> +
> +static int appleib_probe(struct acpi_device *acpi)
> +{
> + struct appleib_device *ib_dev;
> + struct appleib_platform_data *pdata;
Platform_data has a lot of historical meaning in Linux.
Also you have things in here that are not platform related
at all, such as the dev pointer. Hence I would rename it
as device_data or private or something like that.
> + int i;
> + int ret;
> +
> + if (appleib_dev)
This singleton bothers me a bit. I'm really not sure why it
is necessary. You can just put a pointer to this in
the pdata for the subdevs and I think that covers most of your
usecases. It's generally a bad idea to limit things to one instance
of a device unless that actually major simplifications.
I'm not seeing them here.
> + return -EBUSY;
> +
> + ib_dev = appleib_alloc_device(acpi);
> + if (IS_ERR_OR_NULL(ib_dev))
> + return PTR_ERR(ib_dev);
> +
> + ib_dev->subdevs = kmemdup(appleib_subdevs, sizeof(appleib_subdevs),
> + GFP_KERNEL);
Given this is fixed sized and always referenced via ib_dev->subdevs, just
put the array in there and memcpy into it. That way you have one less
allocation and simpler code.
> + if (!ib_dev->subdevs) {
> + ret = -ENOMEM;
> + goto free_dev;
> + }
> +
> + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
Might as well embed this in ib_dev as well. That would let
you used container_of to avoid having to carry the ib_dev pointer
around in side pdata.
> + if (!pdata) {
> + ret = -ENOMEM;
> + goto free_subdevs;
> + }
> +
> + pdata->ib_dev = ib_dev;
> + pdata->log_dev = LOG_DEV(ib_dev);
> + for (i = 0; i < ARRAY_SIZE(appleib_subdevs); i++) {
> + ib_dev->subdevs[i].platform_data = pdata;
> + ib_dev->subdevs[i].pdata_size = sizeof(*pdata);
> + }
> +
> + ret = mfd_add_devices(&acpi->dev, PLATFORM_DEVID_NONE,
> + ib_dev->subdevs, ARRAY_SIZE(appleib_subdevs),
> + NULL, 0, NULL);
> + if (ret) {
> + dev_err(LOG_DEV(ib_dev), "Error adding MFD devices: %d\n", ret);
> + goto free_pdata;
> + }
> +
> + acpi->driver_data = ib_dev;
> + appleib_dev = ib_dev;
> +
> + ret = hid_register_driver(&appleib_hid_driver);
> + if (ret) {
> + dev_err(LOG_DEV(ib_dev), "Error registering hid driver: %d\n",
> + ret);
> + goto rem_mfd_devs;
> + }
> +
> + return 0;
> +
> +rem_mfd_devs:
> + mfd_remove_devices(&acpi->dev);
> +free_pdata:
> + kfree(pdata);
> +free_subdevs:
> + kfree(ib_dev->subdevs);
> +free_dev:
> + appleib_dev = NULL;
> + acpi->driver_data = NULL;
Why at this point? It's not set to anything until much later in the
probe flow. May be worth thinking about devm_ managed allocations
to cleanup some of these allocations automatically and simplify
the error handling.
> + kfree(ib_dev);
> + return ret;
> +}
> +
> +static int appleib_remove(struct acpi_device *acpi)
> +{
> + struct appleib_device *ib_dev = acpi_driver_data(acpi);
> +
> + mfd_remove_devices(&acpi->dev);
> + hid_unregister_driver(&appleib_hid_driver);
> +
> + if (appleib_dev == ib_dev)
From a general reviewability point of view, it's nice to
keep the remove in the same order as the cleanup on
error in probe (and hence reverse of probe). That measn
this should be a little further down.
I'd also like to see a comment on how this condition can be
false.
> + appleib_dev = NULL;
> +
> + kfree(ib_dev->subdevs[0].platform_data);
> + kfree(ib_dev->subdevs);
> + kfree(ib_dev);
Is it worth considering devm in here to avoid the need to
clean all these up by hand?
> +
> + return 0;
> +}
> +
> +static int appleib_suspend(struct device *dev)
> +{
> + struct acpi_device *adev;
> + struct appleib_device *ib_dev;
> + int rc;
> +
> + adev = to_acpi_device(dev);
> + ib_dev = acpi_driver_data(adev);
Given this appears a few times, probably worth the more compact
ib_dev = acpi_driver_data(to_acpi_device(dev));
Allowing you to drop the adev local variable that doesn't add
any info.
> +
> + rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 0);
> + if (ACPI_FAILURE(rc))
> + dev_warn(LOG_DEV(ib_dev), "SOCW(0) failed: %s\n",
I can sort of see you might want to do the LOG_DEV for consistency
but here I'm fairly sure it's just dev which might be clearer.
> + acpi_format_exception(rc));
> +
> + return 0;
> +}
> +
> +static int appleib_resume(struct device *dev)
> +{
> + struct acpi_device *adev;
> + struct appleib_device *ib_dev;
> + int rc;
> +
> + adev = to_acpi_device(dev);
> + ib_dev = acpi_driver_data(adev);
> +
> + rc = acpi_execute_simple_method(ib_dev->asoc_socw, NULL, 1);
> + if (ACPI_FAILURE(rc))
> + dev_warn(LOG_DEV(ib_dev), "SOCW(1) failed: %s\n",
> + acpi_format_exception(rc));
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops appleib_pm = {
> + .suspend = appleib_suspend,
> + .resume = appleib_resume,
> + .restore = appleib_resume,
> +};
> +
> +static const struct acpi_device_id appleib_acpi_match[] = {
> + { "APP7777", 0 },
> + { },
> +};
> +
> +MODULE_DEVICE_TABLE(acpi, appleib_acpi_match);
> +
> +static struct acpi_driver appleib_driver = {
> + .name = "apple-ibridge",
> + .class = "topcase", /* ? */
> + .owner = THIS_MODULE,
> + .ids = appleib_acpi_match,
> + .ops = {
> + .add = appleib_probe,
> + .remove = appleib_remove,
> + },
> + .drv = {
> + .pm = &appleib_pm,
> + },
> +};
> +
> +module_acpi_driver(appleib_driver)
> +
> +MODULE_AUTHOR("Ronald Tschalär");
> +MODULE_DESCRIPTION("Apple iBridge driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/apple-ibridge.h b/include/linux/mfd/apple-ibridge.h
> new file mode 100644
> index 000000000000..d321714767f7
> --- /dev/null
> +++ b/include/linux/mfd/apple-ibridge.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Apple iBridge Driver
> + *
> + * Copyright (c) 2018 Ronald Tschalär
> + */
> +
> +#ifndef __LINUX_MFD_APPLE_IBRDIGE_H
> +#define __LINUX_MFD_APPLE_IBRDIGE_H
> +
> +#include <linux/device.h>
> +#include <linux/hid.h>
> +
> +#define PLAT_NAME_IB_TB "apple-ib-tb"
> +#define PLAT_NAME_IB_ALS "apple-ib-als"
> +
> +struct appleib_device;
> +
> +struct appleib_platform_data {
> + struct appleib_device *ib_dev;
> + struct device *log_dev;
> +};
> +
> +int appleib_register_hid_driver(struct appleib_device *ib_dev,
> + struct hid_driver *driver, void *data);
> +int appleib_unregister_hid_driver(struct appleib_device *ib_dev,
> + struct hid_driver *driver);
> +
> +void *appleib_get_drvdata(struct appleib_device *ib_dev,
> + struct hid_driver *driver);
> +bool appleib_in_hid_probe(struct appleib_device *ib_dev);
> +
> +struct hid_field *appleib_find_report_field(struct hid_report *report,
> + unsigned int field_usage);
> +struct hid_field *appleib_find_hid_field(struct hid_device *hdev,
> + unsigned int application,
> + unsigned int field_usage);
> +
> +#endif
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox