Received: by 2002:ac0:8c9a:0:0:0:0:0 with SMTP id r26csp3522069ima; Mon, 4 Feb 2019 00:20:48 -0800 (PST) X-Google-Smtp-Source: AHgI3IYJyigLE2mJTPsHpjJZRnQxeEtNR5lSeDABRX79Gd1+Y491gTIOrgm2AL/znJZUyp21HC7X X-Received: by 2002:a63:4658:: with SMTP id v24mr718277pgk.114.1549268448416; Mon, 04 Feb 2019 00:20:48 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1549268448; cv=none; d=google.com; s=arc-20160816; b=SNKswSpmUSKTWHDKsKprWj+W8rGZ8o/Z77kJF+fkzDPSHNubgbsQbQ7AsFdchWaU6+ CCz2tHPa+0slsbflDLWyATqtQFVt8kvZAKZgoSjaNMWeZwb8U9G+o9sQ/fSj1l5GUQfd OopLp9vzAlWUAcSvQNwEgwdc4X4bAdNbIG8y0uhJL7uK/P1s1XG7EYEZsmuhBAXlxzmO SuGbAPklhGeGBaVlTab+EmB1T3praxIrj/ux8EqwMRDZ/QuF+a9Iky8f10afORiTzrFJ pLbSl4UH64XyzwfJ0V28xYvUxakxOXK6BEL501TmazLyoBiZLUyciVdIikJMj2/CzdDY u1DA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:dkim-signature :dkim-filter:from; bh=I6t7nEnBCQoOxKOptwOcWRfTzxk8wGwb+SD2DPw8tGU=; b=VhApSQBzZLB625RxXUC45ZcYl+fvj1qwD74f7otBWI/f0WUqA6uJ3a1CXlnWFsFFUh JITeZBjxOaCv96brhtEYlrS3ZKVvqzW0J6YRxSIQTwdkkBlcRYLSICb3MWiPX9IMX0ko Ew3cVbDhqGUT73pjobmLIH4mUPVlcVAot8VCvfTORMZLqZ4+YwGfigGlmQCarn3mX2HO OY3hC4lBgnTGXr40w2VMN+FZ1zVBFkMd7oaSxUw4MtFqAfk/C7KT+QWTzU2jfQ0mr+7Z /mQfpiDSx928KRhR2PUdhfFe1QTrdFS440I/i+nzxwlPMkVaboOCDa1uLd+m3rQrA1n6 rKlQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@innovation.ch header.s=default header.b=EZ98mGCO; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=innovation.ch Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id m4si14970519pgj.61.2019.02.04.00.20.32; Mon, 04 Feb 2019 00:20:48 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@innovation.ch header.s=default header.b=EZ98mGCO; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=innovation.ch Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728345AbfBDIUG (ORCPT + 99 others); Mon, 4 Feb 2019 03:20:06 -0500 Received: from chill.innovation.ch ([216.218.245.220]:56446 "EHLO chill.innovation.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727756AbfBDIUE (ORCPT ); Mon, 4 Feb 2019 03:20:04 -0500 Received: from localhost (localhost [127.0.0.1]) by chill.innovation.ch (Postfix) with ESMTP id 1C95C64013F; Mon, 4 Feb 2019 00:20:03 -0800 (PST) X-Virus-Scanned: amavisd-new at Received: from chill.innovation.ch ([127.0.0.1]) by localhost (chill.innovation.ch [127.0.0.1]) (amavisd-new, port 10024) with LMTP id 5GMVfUDmdfQH; Mon, 4 Feb 2019 00:19:55 -0800 (PST) From: =?UTF-8?q?Ronald=20Tschal=C3=A4r?= DKIM-Filter: OpenDKIM Filter v2.10.3 chill.innovation.ch 9DCB0640134 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=innovation.ch; s=default; t=1549268393; bh=I6t7nEnBCQoOxKOptwOcWRfTzxk8wGwb+SD2DPw8tGU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EZ98mGCOpt0MkP99w3dmDqNvc+qS3y6qgGBG5kIDHKimEs47zaFtC7fDaE/5+5T+u w711wOIYohQtY4esl6gqWFNPso2Ov6jVvZws2nI/NlRp8F9WQENGauWqHLdN71SI1z B1V16E+mZf1hY2mYHouUJlZTkSInG3G30Y5SUnZMPRiY4m9CRHfs9hkzfOmVLLokKM hsiSHk8xJ6ta/5a90Bnvrs0SIk20C/UQhjHdyeBlCOJqvzxNcqUMm+lFjJqzoMUU7u dl8uoviP0izltmdaddQ8s1LvXjqrde35L4MYUZe/3ZL9IO37GF97g/iQIKscJkf9x5 V/uShlD3E219w== To: Dmitry Torokhov , Henrik Rydberg Cc: Lukas Wunner , Federico Lorenzi , Andy Shevchenko , linux-input@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/2] Input: add Apple SPI keyboard and trackpad driver. Date: Mon, 4 Feb 2019 00:19:47 -0800 Message-Id: <20190204081947.25152-3-ronald@innovation.ch> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190204081947.25152-1-ronald@innovation.ch> References: <20190204081947.25152-1-ronald@innovation.ch> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 CC: Lukas Wunner CC: Andy Shevchenko 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 --- drivers/input/keyboard/Kconfig | 13 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/applespi.c | 1919 +++++++++++++++++++++++++++++ 3 files changed, 1933 insertions(+) create mode 100644 drivers/input/keyboard/applespi.c diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index a878351f1643..e35afa23b1db 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -70,6 +70,19 @@ config KEYBOARD_AMIGA config ATARI_KBD_CORE bool +config KEYBOARD_APPLESPI + tristate "Apple SPI keyboard and trackpad" + depends on (X86 && ACPI && SPI) || 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..75780f3385c0 --- /dev/null +++ b/drivers/input/keyboard/applespi.c @@ -0,0 +1,1919 @@ +// 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. + */ + +#define pr_fmt(fmt) "applespi: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_MODIFIERS 8 + +#define MAX_FINGERS 11 +#define MAX_FINGER_ORIENTATION 16384 +#define MAX_PKTS_PER_MSG 2 + +#define MIN_KBD_BL_LEVEL 32 +#define MAX_KBD_BL_LEVEL 255 +#define KBD_BL_LEVEL_SCALE 1000000 +#define KBD_BL_LEVEL_ADJ \ + ((MAX_KBD_BL_LEVEL - MIN_KBD_BL_LEVEL) * KBD_BL_LEVEL_SCALE / 255) + +#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 DBG_CMD_TP_INI BIT(0) +#define DBG_CMD_BL BIT(1) +#define DBG_CMD_CL BIT(2) +#define DBG_RD_KEYB BIT(8) +#define DBG_RD_TPAD BIT(9) +#define DBG_RD_UNKN BIT(10) +#define DBG_RD_IRQ BIT(11) +#define DBG_RD_CRC BIT(12) +#define DBG_TP_DIM BIT(16) + +#define debug_print(mask, fmt, ...) \ + do { \ + if (debug & mask) \ + printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__); \ + } while (0) + +#define debug_print_header(mask) \ + debug_print(mask, "--- %s ---------------------------\n", \ + applespi_debug_facility(mask)) + +#define debug_print_buffer(mask, fmt, ...) \ + do { \ + if (debug & mask) \ + print_hex_dump(KERN_DEBUG, pr_fmt(fmt), \ + DUMP_PREFIX_NONE, 32, 1, ##__VA_ARGS__, \ + false); \ + } while (0) + +#define APPLE_FLAG_FKEY 0x01 + +#define SPI_RW_CHG_DLY 100 /* from experimentation, in us */ + +#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 unsigned int iso_layout; +module_param(iso_layout, uint, 0644); +MODULE_PARM_DESC(iso_layout, "Enable/Disable hardcoded ISO-layout of the keyboard. ([0] = disabled, 1 = enabled)"); + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "Enable/Disable debug logging. This is a bitmask."); + +static int touchpad_dimensions[4]; +module_param_array(touchpad_dimensions, int, NULL, 0444); +MODULE_PARM_DESC(touchpad_dimensions, "The pixel dimensions of the touchpad, as x_min,x_max,y_min,y_max ."); + +/** + * 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 + * @crc_16: crc over the whole message struct (message header + + * this struct) minus this @crc_16 field + */ +struct keyboard_protocol { + __u8 unknown1; + __u8 modifiers; + __u8 unknown2; + __u8 keys_pressed[MAX_ROLLOVER]; + __u8 fn_pressed; + __le16 crc_16; +}; + +/** + * 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 + * @crc_16: on last finger: crc over the whole message struct + * (i.e. message header + this struct) minus the last + * @crc_16 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 crc_16; +}; + +/** + * 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 + * + * @crc_16: crc over the whole message struct (message header + + * this struct) minus this @crc_16 field + */ +struct command_protocol_tp_info { + __le16 crc_16; +}; + +/** + * struct touchpad_info - touchpad info response. + * message.type = 0x1020, message.length = 0x006e + * + * @unknown1: unknown + * @model_id: the touchpad model number + * @unknown2: unknown + * @crc_16: crc over the whole message struct (message header + + * this struct) minus this @crc_16 field + */ +struct touchpad_info_protocol { + __u8 unknown1[105]; + __le16 model_id; + __u8 unknown2[3]; + __le16 crc_16; +} __packed; + +/** + * struct command_protocol_mt_init - initialize multitouch. + * message.type = 0x0252, message.length = 0x0002 + * + * @cmd: value: 0x0102 + * @crc_16: crc over the whole message struct (message header + + * this struct) minus this @crc_16 field + */ +struct command_protocol_mt_init { + __le16 cmd; + __le16 crc_16; +}; + +/** + * struct command_protocol_capsl - toggle caps-lock led + * message.type = 0x0151, message.length = 0x0002 + * + * @unknown: value: 0x01 (length?) + * @led: 0 off, 2 on + * @crc_16: crc over the whole message struct (message header + + * this struct) minus this @crc_16 field + */ +struct command_protocol_capsl { + __u8 unknown; + __u8 led; + __le16 crc_16; +}; + +/** + * 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) + * @crc_16: crc over the whole message struct (message header + + * this struct) minus this @crc_16 field + */ +struct command_protocol_bl { + __le16 const1; + __le16 level; + __le16 const2; + __le16 crc_16; +}; + +/** + * 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 + * @crc_16: crc over this whole structure minus this @crc_16 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 crc_16; +}; + +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) */ +}; + +struct applespi_tp_info { + int x_min; + int x_max; + int y_min; + 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]; + acpi_handle handle; + 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; + unsigned int cmd_log_mask; + + 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; +}; + +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 +}; + +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 = 0x0417, /* MB8 MB9 MB10 */ + .tp_info = { -5087, 5579, -182, 6089 }, + }, + { + .model = 0x0557, /* MBP13,1 MBP13,2 MBP14,1 MBP14,2 */ + .tp_info = { -6243, 6749, -170, 7685 }, + }, + { + .model = 0x06d7, /* MBP13,3 MBP14,3 */ + .tp_info = { -7456, 7976, -163, 9283 }, + }, + {} +}; + +static const char *applespi_debug_facility(unsigned int log_mask) +{ + switch (log_mask) { + case DBG_CMD_TP_INI: + return "Touchpad Initialization"; + case DBG_CMD_BL: + return "Backlight Command"; + case DBG_CMD_CL: + return "Caps-Lock Command"; + case DBG_RD_KEYB: + return "Keyboard Event"; + case DBG_RD_TPAD: + return "Touchpad Event"; + case DBG_RD_UNKN: + return "Unknown Event"; + case DBG_RD_IRQ: + return "Interrupt Request"; + case DBG_RD_CRC: + return "Corrupted packet"; + case DBG_TP_DIM: + return "Touchpad Dimensions"; + default: + return "-Unknown-"; + } +} + +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_DLY; + 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_DLY; + + 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 sts_ok[] = { 0xac, 0x27, 0x68, 0xd5 }; + bool ret = true; + + if (sts < 0) { + ret = false; + pr_warn("Error writing to device: %d\n", sts); + } else if (memcmp(applespi->tx_status, sts_ok, + APPLESPI_STATUS_SIZE) != 0) { + ret = false; + pr_warn("Error writing to device: %x %x %x %x\n", + applespi->tx_status[0], applespi->tx_status[1], + applespi->tx_status[2], applespi->tx_status[3]); + } + + return ret; +} + +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 + pr_warn("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 + pr_warn("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 + pr_warn("Property resetRecUsec not found\n"); + + pr_debug("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) +{ + int result; + unsigned long long spi_status; + + /* check if SPI is already enabled, so we can skip the delay below */ + result = acpi_evaluate_integer(applespi->sist, NULL, NULL, &spi_status); + if (ACPI_SUCCESS(result) && spi_status) + return 0; + + /* SIEN(1) will enable SPI communication */ + result = acpi_execute_simple_method(applespi->sien, NULL, 1); + if (ACPI_FAILURE(result)) { + pr_err("SIEN failed: %s\n", acpi_format_exception(result)); + 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; + + debug_print_header(applespi->cmd_log_mask); + debug_print_buffer(applespi->cmd_log_mask, "write ", + applespi->tx_buffer, APPLESPI_PACKET_SIZE); + debug_print_buffer(applespi->cmd_log_mask, "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_log_mask = DBG_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_log_mask = DBG_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_log_mask = DBG_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_log_mask = DBG_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++ & 0xff; + + 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); + *((__le16 *)&message->data[msg_len - 2]) = cpu_to_le16(crc); + + crc = crc16(0, (u8 *)packet, sizeof(*packet) - 2); + packet->crc_16 = cpu_to_le16(crc); + + /* send command */ + sts = applespi_async(applespi, &applespi->wr_m, + applespi_async_write_complete); + + if (sts != 0) { + pr_warn("Error queueing async write to device: %d\n", sts); + } else { + applespi->cmd_msg_queued = true; + applespi->write_active = true; + } + + return sts; +} + +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_tp_info_cmd = true; + else + applespi->want_mt_init_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 = (unsigned int) + ((value * KBD_BL_LEVEL_ADJ) / KBD_BL_LEVEL_SCALE + + MIN_KBD_BL_LEVEL); + + 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 -1; +} + +/* lifted from the BCM5974 driver */ +/* convert 16-bit little endian to signed integer */ +static inline int raw2int(__le16 x) +{ + return (signed short)le16_to_cpu(x); +} + +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, + raw2int(f->touch_major) << 1); + input_report_abs(input, ABS_MT_TOUCH_MINOR, + raw2int(f->touch_minor) << 1); + input_report_abs(input, ABS_MT_WIDTH_MAJOR, + raw2int(f->tool_major) << 1); + input_report_abs(input, ABS_MT_WIDTH_MINOR, + raw2int(f->tool_minor) << 1); + input_report_abs(input, ABS_MT_ORIENTATION, + MAX_FINGER_ORIENTATION - raw2int(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) +{ + static int min_x, max_x, min_y, max_y; + static bool dim_updated; + static ktime_t last_print; + + 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 (raw2int(f->touch_major) == 0) + continue; + applespi->pos[n].x = raw2int(f->abs_x); + applespi->pos[n].y = tp_info->y_min + tp_info->y_max - + raw2int(f->abs_y); + n++; + + if (debug & DBG_TP_DIM) { + #define UPDATE_DIMENSIONS(val, op, last) \ + do { \ + if (raw2int(val) op last) { \ + last = raw2int(val); \ + dim_updated = true; \ + } \ + } while (0) + + UPDATE_DIMENSIONS(f->abs_x, <, min_x); + UPDATE_DIMENSIONS(f->abs_x, >, max_x); + UPDATE_DIMENSIONS(f->abs_y, <, min_y); + UPDATE_DIMENSIONS(f->abs_y, >, max_y); + } + } + + if (debug & DBG_TP_DIM) { + if (dim_updated && + ktime_ms_delta(ktime_get(), last_print) > 1000) { + printk(KERN_DEBUG + pr_fmt("New touchpad dimensions: %d %d %d %d\n"), + min_x, max_x, min_y, max_y); + dim_updated = false; + last_print = ktime_get(); + } + } + + 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_code_to_key(u8 code, int fn_pressed) +{ + unsigned int key = applespi_scancodes[code]; + const struct applespi_key_translation *trans; + + if (fnmode) { + 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; + } + } + + if (iso_layout) { + trans = applespi_find_translation(apple_iso_keyboard, key); + if (trans) + key = trans->to; + } + + return key; +} + +static void applespi_remap_fn_key(struct keyboard_protocol + *keyboard_protocol) +{ + unsigned char tmp; + unsigned long *modifiers = (unsigned long *) + &keyboard_protocol->modifiers; + + if (!fnremap || fnremap > ARRAY_SIZE(applespi_controlcodes) || + !applespi_controlcodes[fnremap - 1]) + return; + + tmp = keyboard_protocol->fn_pressed; + keyboard_protocol->fn_pressed = test_bit(fnremap - 1, modifiers); + if (tmp) + __set_bit(fnremap - 1, modifiers); + else + __clear_bit(fnremap - 1, modifiers); +} + +static void applespi_handle_keyboard_event(struct applespi_data *applespi, + struct keyboard_protocol + *keyboard_protocol) +{ + int i, j; + unsigned int key; + bool still_pressed; + bool is_overflow; + + /* check for rollover overflow, which is signalled by all keys == 1 */ + is_overflow = true; + + for (i = 0; i < MAX_ROLLOVER; i++) { + if (keyboard_protocol->keys_pressed[i] != 1) { + is_overflow = false; + break; + } + } + + if (is_overflow) + return; + + /* remap fn key if desired */ + applespi_remap_fn_key(keyboard_protocol); + + /* check released keys */ + for (i = 0; i < MAX_ROLLOVER; i++) { + still_pressed = false; + for (j = 0; j < MAX_ROLLOVER; j++) { + if (applespi->last_keys_pressed[i] == + keyboard_protocol->keys_pressed[j]) { + still_pressed = true; + break; + } + } + + if (!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 < MAX_MODIFIERS; i++) { + u8 *modifiers = &keyboard_protocol->modifiers; + + if (test_bit(i, (unsigned long *)modifiers)) + 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(u16 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 void 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 res; + + /* set up touchpad dimensions */ + tp_info = applespi_find_touchpad_info(rcvd_tp_info->model_id); + if (!tp_info) { + pr_warn("Unknown touchpad model %x - falling back to MB8 touchpad\n", + rcvd_tp_info->model_id); + tp_info = &applespi_tp_models[0].tp_info; + } + + applespi->tp_info = *tp_info; + + if (touchpad_dimensions[0] || touchpad_dimensions[1] || + touchpad_dimensions[2] || touchpad_dimensions[3]) { + pr_info("Overriding touchpad dimensions from module param\n"); + applespi->tp_info.x_min = touchpad_dimensions[0]; + applespi->tp_info.x_max = touchpad_dimensions[1]; + applespi->tp_info.y_min = touchpad_dimensions[2]; + applespi->tp_info.y_max = touchpad_dimensions[3]; + } else { + touchpad_dimensions[0] = applespi->tp_info.x_min; + touchpad_dimensions[1] = applespi->tp_info.x_max; + touchpad_dimensions[2] = applespi->tp_info.y_min; + touchpad_dimensions[3] = applespi->tp_info.y_max; + } + + /* create touchpad input device */ + touchpad_input_dev = devm_input_allocate_device(&applespi->spi->dev); + + if (!touchpad_input_dev) { + pr_err("Failed to allocate touchpad input device\n"); + return; + } + + 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_id; + + /* 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 */ + res = input_register_device(touchpad_input_dev); + if (res) + pr_err("Unabled to register touchpad input device (%d)\n", res); + else + /* touchpad_input_dev is read async in spi callback */ + smp_store_release(&applespi->touchpad_input_dev, + touchpad_input_dev); +} + +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) + pr_info("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 != 0) { + dev_warn_ratelimited(&applespi->spi->dev, + "Received corrupted packet (crc mismatch)\n"); + debug_print_header(DBG_RD_CRC); + debug_print_buffer(DBG_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 dbg_mask; + + if (packet->flags == PACKET_TYPE_READ && + packet->device == PACKET_DEV_KEYB) + dbg_mask = DBG_RD_KEYB; + else if (packet->flags == PACKET_TYPE_READ && + packet->device == PACKET_DEV_TPAD) + dbg_mask = DBG_RD_TPAD; + else if (packet->flags == PACKET_TYPE_WRITE) + dbg_mask = applespi->cmd_log_mask; + else + dbg_mask = DBG_RD_UNKN; + + debug_print_header(dbg_mask); + debug_print_buffer(dbg_mask, "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)\n"); + goto cleanup; + } + + /* 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 cleanup; + } + + 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 cleanup; + } + + 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 cleanup; + } + + 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 cleanup; + + if (le16_to_cpu(message->length) != msg_len - MSG_HEADER_SIZE - 2) { + dev_warn_ratelimited(&applespi->spi->dev, + "Received corrupted packet (invalid message length)\n"); + goto cleanup; + } + + /* 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 = &message->touchpad; + + size_t 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)\n"); + goto cleanup; + } + + 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); + } + +cleanup: + /* clean up */ + 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) { + pr_warn("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; + + debug_print_header(DBG_RD_IRQ); + + spin_lock_irqsave(&applespi->cmd_msg_lock, flags); + + if (!applespi->suspended) { + sts = applespi_async(applespi, &applespi->rd_m, + applespi_async_read_complete); + if (sts != 0) + pr_warn("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(void) +{ + 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 -1; + + 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) + pr_warn("Error getting backlight level from EFI vars: %d\n", + sts); + + kfree(efivar_entry); + + return efi_data; +} + +static void applespi_save_bl_level(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) + pr_warn("Error saving backlight level to EFI vars: %d\n", sts); +} + +static int applespi_probe(struct spi_device *spi) +{ + struct applespi_data *applespi; + int result, i; + unsigned long long gpe, usb_status; + + /* check if the USB interface is present and enabled already */ + result = acpi_evaluate_integer(ACPI_HANDLE(&spi->dev), "UIST", NULL, + &usb_status); + if (ACPI_SUCCESS(result) && usb_status) { + /* let the USB driver take over instead */ + pr_info("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; + applespi->handle = ACPI_HANDLE(&spi->dev); + + 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(&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 */ + if (ACPI_FAILURE(acpi_get_handle(applespi->handle, "SIEN", + &applespi->sien)) || + ACPI_FAILURE(acpi_get_handle(applespi->handle, "SIST", + &applespi->sist))) { + pr_err("Failed to get required ACPI method handle\n"); + return -ENODEV; + } + + /* switch on the SPI interface */ + result = applespi_setup_spi(applespi); + if (result) + return result; + + result = applespi_enable_spi(applespi); + if (result) + return result; + + /* 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); + + result = input_register_device(applespi->keyboard_input_dev); + if (result) { + pr_err("Unabled to register keyboard input device (%d)\n", + result); + return -ENODEV; + } + + /* + * The applespi device doesn't send interrupts normally (as is described + * in its DSDT), but rather seems to use ACPI GPEs. + */ + result = acpi_evaluate_integer(applespi->handle, "_GPE", NULL, &gpe); + if (ACPI_FAILURE(result)) { + pr_err("Failed to obtain GPE for SPI slave device: %s\n", + acpi_format_exception(result)); + return -ENODEV; + } + applespi->gpe = (int)gpe; + + result = acpi_install_gpe_handler(NULL, applespi->gpe, + ACPI_GPE_LEVEL_TRIGGERED, + applespi_notify, applespi); + if (ACPI_FAILURE(result)) { + pr_err("Failed to install GPE handler for GPE %d: %s\n", + applespi->gpe, acpi_format_exception(result)); + return -ENODEV; + } + + applespi->suspended = false; + + result = acpi_enable_gpe(NULL, applespi->gpe); + if (ACPI_FAILURE(result)) { + pr_err("Failed to enable GPE handler for GPE %d: %s\n", + applespi->gpe, acpi_format_exception(result)); + acpi_remove_gpe_handler(NULL, applespi->gpe, applespi_notify); + return -ENODEV; + } + + /* trigger touchpad setup */ + applespi_init(applespi, false); + + /* + * By default this device is not enable 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 */ + result = applespi_get_saved_bl_level(); + if (result >= 0) + applespi_set_bl_level(&applespi->backlight_info, result); + + applespi->backlight_info.name = "spi::kbd_backlight"; + applespi->backlight_info.default_trigger = "kbd-backlight"; + applespi->backlight_info.brightness_set = applespi_set_bl_level; + + result = devm_led_classdev_register(&spi->dev, + &applespi->backlight_info); + if (result) { + pr_err("Unable to register keyboard backlight class dev (%d)\n", + result); + /* not fatal */ + } + + /* done */ + pr_info("spi-device probe done: %s\n", dev_name(&spi->dev)); + + return 0; +} + +static int applespi_remove(struct spi_device *spi) +{ + struct applespi_data *applespi = spi_get_drvdata(spi); + unsigned long flags; + + /* wait for all outstanding writes to finish */ + 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); + + /* shut things down */ + acpi_disable_gpe(NULL, applespi->gpe); + acpi_remove_gpe_handler(NULL, applespi->gpe, applespi_notify); + + /* wait for all outstanding reads to finish */ + spin_lock_irqsave(&applespi->cmd_msg_lock, flags); + + wait_event_lock_irq(applespi->drain_complete, !applespi->read_active, + applespi->cmd_msg_lock); + + spin_unlock_irqrestore(&applespi->cmd_msg_lock, flags); + + /* done */ + pr_info("spi-device remove done: %s\n", dev_name(&spi->dev)); + return 0; +} + +static void applespi_shutdown(struct spi_device *spi) +{ + struct applespi_data *applespi = spi_get_drvdata(spi); + + applespi_save_bl_level(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->have_bl_level); + + return 0; +} + +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 status; + unsigned long flags; + int rc; + + /* turn off caps-lock - it'll stay on otherwise */ + rc = applespi_set_capsl_led(applespi, false); + if (rc) + pr_warn("Failed to turn off caps-lock led (%d)\n", rc); + + /* wait for all outstanding writes to finish */ + 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); + + /* disable the interrupt */ + status = acpi_disable_gpe(NULL, applespi->gpe); + if (ACPI_FAILURE(status)) { + pr_err("Failed to disable GPE handler for GPE %d: %s\n", + applespi->gpe, acpi_format_exception(status)); + } + + /* wait for all outstanding reads to finish */ + 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); + + pr_info("spi-device suspend done.\n"); + 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 status; + 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 */ + status = acpi_enable_gpe(NULL, applespi->gpe); + if (ACPI_FAILURE(status)) { + pr_err("Failed to re-enable GPE handler for GPE %d: %s\n", + applespi->gpe, acpi_format_exception(status)); + } + + /* switch the touchpad into multitouch mode */ + applespi_init(applespi, true); + + pr_info("spi-device resume done.\n"); + + return 0; +} + +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", + .owner = THIS_MODULE, + + .acpi_match_table = ACPI_PTR(applespi_acpi_match), + .pm = &applespi_pm_ops, + }, + .probe = applespi_probe, + .remove = applespi_remove, + .shutdown = applespi_shutdown, +}; + +module_spi_driver(applespi_driver) + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MacBook(Pro) SPI Keyboard/Touchpad driver"); +MODULE_AUTHOR("Federico Lorenzi"); +MODULE_AUTHOR("Ronald Tschalär"); -- 2.20.1