Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH v3 4/4] Input: add Apple SPI keyboard and trackpad driver.
From: Ronald Tschalär @ 2019-03-27  1:48 UTC (permalink / raw)
  To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko,
	Sergey Senozhatsky, Steven Rostedt, Greg Kroah-Hartman,
	Rafael J. Wysocki
  Cc: Lukas Wunner, Federico Lorenzi, linux-input, linux-kernel
In-Reply-To: <20190327014807.7472-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>
---
 drivers/input/keyboard/Kconfig    |   15 +
 drivers/input/keyboard/Makefile   |    1 +
 drivers/input/keyboard/applespi.c | 1988 +++++++++++++++++++++++++++++
 3 files changed, 2004 insertions(+)
 create mode 100644 drivers/input/keyboard/applespi.c

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..39e38d98869e
--- /dev/null
+++ b/drivers/input/keyboard/applespi.c
@@ -0,0 +1,1988 @@
+// 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/delay.h>
+#include <linux/efi.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/ktime.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-generic/unaligned.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 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, applespi, fmt, ...) \
+	do { \
+		if (debug & (mask)) \
+			dev_printk(KERN_DEBUG, &(applespi)->spi->dev, fmt, \
+				   ##__VA_ARGS__); \
+	} while (0)
+
+#define	debug_print_header(mask, applespi) \
+	debug_print(mask, applespi, "--- %s ---------------------------\n", \
+		    applespi_debug_facility(mask))
+
+#define	debug_print_buffer(mask, applespi, fmt, buf, len) \
+	do { \
+		if (debug & (mask)) \
+			dev_print_hex_dump(KERN_DEBUG, &(applespi)->spi->dev, \
+					   fmt, DUMP_PREFIX_NONE, 32, 1, buf, \
+					   len, false); \
+	} while (0)
+
+#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 unsigned int debug;
+module_param(debug, uint, 0644);
+MODULE_PARM_DESC(debug, "Enable/Disable debug logging. This is a bitmask.");
+
+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];
+	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
+};
+
+/*
+ * 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 },
+	},
+	{}
+};
+
+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 "-Unrecognized log mask-";
+	}
+}
+
+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;
+
+	debug_print_header(applespi->cmd_log_mask, applespi);
+	debug_print_buffer(applespi->cmd_log_mask, applespi, "write  ",
+			   applespi->tx_buffer, APPLESPI_PACKET_SIZE);
+	debug_print_buffer(applespi->cmd_log_mask, applespi, "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++ % (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 int applespi_dbg_dim_min_x;
+static int applespi_dbg_dim_max_x;
+static int applespi_dbg_dim_min_y;
+static int applespi_dbg_dim_max_y;
+static bool applespi_dbg_dim_updated;
+
+static void applespi_debug_update_dimensions(const struct tp_finger *f)
+{
+	#define UPDATE_DIMENSIONS(val, op, last) \
+		do { \
+			if (le16_to_int(val) op last) { \
+				last = le16_to_int(val); \
+				applespi_dbg_dim_updated = true; \
+			} \
+		} while (0)
+
+	UPDATE_DIMENSIONS(f->abs_x, <, applespi_dbg_dim_min_x);
+	UPDATE_DIMENSIONS(f->abs_x, >, applespi_dbg_dim_max_x);
+	UPDATE_DIMENSIONS(f->abs_y, <, applespi_dbg_dim_min_y);
+	UPDATE_DIMENSIONS(f->abs_y, >, applespi_dbg_dim_max_y);
+
+	#undef UPDATE_DIMENSIONS
+}
+
+static void applespi_debug_print_dimensions(struct applespi_data *applespi)
+{
+	static ktime_t last_print;
+
+	if (applespi_dbg_dim_updated &&
+	    ktime_ms_delta(ktime_get(), last_print) > 1000) {
+		debug_print(DBG_TP_DIM, applespi,
+			    "New touchpad dimensions: %dx%d+%u+%u\n",
+			    applespi_dbg_dim_min_x, applespi_dbg_dim_min_y,
+			    applespi_dbg_dim_max_x - applespi_dbg_dim_min_x,
+			    applespi_dbg_dim_max_y - applespi_dbg_dim_min_y);
+		applespi_dbg_dim_updated = false;
+		last_print = ktime_get();
+	}
+}
+
+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 (debug & DBG_TP_DIM)
+			applespi_debug_update_dimensions(f);
+	}
+
+	if (debug & DBG_TP_DIM)
+		applespi_debug_print_dimensions(applespi);
+
+	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;
+
+	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(fnremap - 1));
+	if (tmp)
+		keyboard_protocol->modifiers |= BIT(fnremap - 1);
+	else
+		keyboard_protocol->modifiers &= ~BIT(fnremap - 1);
+}
+
+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;
+
+	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 */
+	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)
+			continue;
+
+		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");
+		debug_print_header(DBG_RD_CRC, applespi);
+		debug_print_buffer(DBG_RD_CRC, applespi, "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, applespi);
+	debug_print_buffer(dbg_mask, applespi, "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;
+
+	debug_print_header(DBG_RD_IRQ, applespi);
+
+	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_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(ACPI_HANDLE(&spi->dev), "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;
+	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_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 */
+	if (ACPI_FAILURE(acpi_get_handle(applespi->handle, "SIEN",
+					 &applespi->sien)) ||
+	    ACPI_FAILURE(acpi_get_handle(applespi->handle, "SIST",
+					 &applespi->sist))) {
+		dev_err(&(applespi)->spi->dev,
+			"Failed to get required ACPI method handles\n");
+		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(applespi->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);
+
+	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);
+
+	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");
-- 
2.20.1

^ permalink raw reply related

* [PATCH v3 3/4] driver core: add dev_print_hex_dump() logging function.
From: Ronald Tschalär @ 2019-03-27  1:48 UTC (permalink / raw)
  To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko,
	Sergey Senozhatsky, Steven Rostedt, Greg Kroah-Hartman,
	Rafael J. Wysocki
  Cc: Lukas Wunner, Federico Lorenzi, linux-input, linux-kernel
In-Reply-To: <20190327014807.7472-1-ronald@innovation.ch>

This is the dev_xxx() analog to print_hex_dump(), using dev_printk()
instead of straight printk() to match other dev_xxx() logging functions.
---
 drivers/base/core.c    | 43 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/device.h | 15 +++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 0073b09bb99f..eb260d482750 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -3097,6 +3097,49 @@ define_dev_printk_level(_dev_warn, KERN_WARNING);
 define_dev_printk_level(_dev_notice, KERN_NOTICE);
 define_dev_printk_level(_dev_info, KERN_INFO);
 
+static void
+print_to_dev_printk(const char *level, void *arg, const char *fmt, ...)
+{
+	struct va_format vaf;
+	va_list args;
+
+	va_start(args, fmt);
+
+	vaf.fmt = fmt;
+	vaf.va = &args;
+
+	__dev_printk(level, (const struct device *)arg, &vaf);
+
+	va_end(args);
+}
+
+/**
+ * _dev_print_hex_dump - print a text hex dump to syslog for a binary blob of
+ * data
+ * @level: kernel log level (e.g. KERN_DEBUG)
+ * @dev: the device to which this log message pertains
+ * @prefix_str: string to prefix each line with;
+ *  caller supplies trailing spaces for alignment if desired
+ * @prefix_type: controls whether prefix of an offset, address, or none
+ *  is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE)
+ * @rowsize: number of bytes to print per line; must be 16 or 32
+ * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
+ * @buf: data blob to dump
+ * @len: number of bytes in the @buf
+ * @ascii: include ASCII after the hex output
+ *
+ * See print_hex_dump() for additional details and examples.
+ */
+void _dev_print_hex_dump(const char *level, const struct device *dev,
+			 const char *prefix_str, int prefix_type,
+			 int rowsize, int groupsize,
+			 const void *buf, size_t len, bool ascii)
+{
+	print_hex_dump_to_cb(level, prefix_str, prefix_type, rowsize, groupsize,
+			     buf, len, ascii, print_to_dev_printk, (void *)dev);
+}
+EXPORT_SYMBOL(_dev_print_hex_dump);
+
 #endif
 
 static inline bool fwnode_is_primary(struct fwnode_handle *fwnode)
diff --git a/include/linux/device.h b/include/linux/device.h
index 6cb4640b6160..dc6fcae3002a 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -1405,6 +1405,10 @@ __printf(2, 3)
 void _dev_notice(const struct device *dev, const char *fmt, ...);
 __printf(2, 3)
 void _dev_info(const struct device *dev, const char *fmt, ...);
+void _dev_print_hex_dump(const char *level, const struct device *dev,
+			 const char *prefix_str, int prefix_type,
+			 int rowsize, int groupsize,
+			 const void *buf, size_t len, bool ascii);
 
 #else
 
@@ -1445,6 +1449,12 @@ void _dev_notice(const struct device *dev, const char *fmt, ...)
 static inline __printf(2, 3)
 void _dev_info(const struct device *dev, const char *fmt, ...)
 {}
+static inline
+void _dev_print_hex_dump(const char *level, const struct device *dev,
+			 const char *prefix_str, int prefix_type,
+			 int rowsize, int groupsize,
+			 const void *buf, size_t len, bool ascii);
+{}
 
 #endif
 
@@ -1467,6 +1477,11 @@ void _dev_info(const struct device *dev, const char *fmt, ...)
 	_dev_notice(dev, dev_fmt(fmt), ##__VA_ARGS__)
 #define dev_info(dev, fmt, ...)						\
 	_dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)
+#define dev_print_hex_dump(level, dev, prefix_str, prefix_type,		\
+			   rowsize, groupsize, buf, len, ascii)		\
+	_dev_print_hex_dump(level, dev, dev_fmt(prefix_str),		\
+			    prefix_type, rowsize, groupsize, buf, len,	\
+			    ascii);
 
 #if defined(CONFIG_DYNAMIC_DEBUG)
 #define dev_dbg(dev, fmt, ...)						\
-- 
2.20.1

^ permalink raw reply related

* [PATCH v3 2/4] lib/hexdump.c: factor out generic hexdump formatting for reuse.
From: Ronald Tschalär @ 2019-03-27  1:48 UTC (permalink / raw)
  To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko,
	Sergey Senozhatsky, Steven Rostedt, Greg Kroah-Hartman,
	Rafael J. Wysocki
  Cc: Lukas Wunner, Federico Lorenzi, linux-input, linux-kernel
In-Reply-To: <20190327014807.7472-1-ronald@innovation.ch>

This introduces print_hex_dump_to_cb() which contains all the hexdump
formatting minus the actual printk() call, allowing an arbitrary print
function to be supplied instead. And print_hex_dump() is re-implemented
using print_hex_dump_to_cb().

This allows other hex-dump logging functions to be provided which call
printk() differently or even log the hexdump somewhere entirely
different.
---
 include/linux/printk.h | 12 ++++++
 lib/hexdump.c          | 95 +++++++++++++++++++++++++++++++-----------
 2 files changed, 83 insertions(+), 24 deletions(-)

diff --git a/include/linux/printk.h b/include/linux/printk.h
index 77740a506ebb..4ebdacd7a287 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -483,10 +483,16 @@ enum {
 extern int hex_dump_to_buffer(const void *buf, size_t len, int rowsize,
 			      int groupsize, char *linebuf, size_t linebuflen,
 			      bool ascii);
+typedef
+void (*hex_dump_callback)(const char *level, void *arg, const char *fmt, ...);
 #ifdef CONFIG_PRINTK
 extern void print_hex_dump(const char *level, const char *prefix_str,
 			   int prefix_type, int rowsize, int groupsize,
 			   const void *buf, size_t len, bool ascii);
+extern void print_hex_dump_to_cb(const char *level, const char *prefix_str,
+				 int prefix_type, int rowsize, int groupsize,
+				 const void *buf, size_t len, bool ascii,
+				 hex_dump_callback print, void *print_arg);
 #if defined(CONFIG_DYNAMIC_DEBUG)
 #define print_hex_dump_bytes(prefix_str, prefix_type, buf, len)	\
 	dynamic_hex_dump(prefix_str, prefix_type, 16, 1, buf, len, true)
@@ -500,6 +506,12 @@ static inline void print_hex_dump(const char *level, const char *prefix_str,
 				  const void *buf, size_t len, bool ascii)
 {
 }
+extern void print_hex_dump_to_cb(const char *level, const char *prefix_str,
+				 int prefix_type, int rowsize, int groupsize,
+				 const void *buf, size_t len, bool ascii,
+				 hex_dump_callback *print, void *print_arg);
+{
+}
 static inline void print_hex_dump_bytes(const char *prefix_str, int prefix_type,
 					const void *buf, size_t len)
 {
diff --git a/lib/hexdump.c b/lib/hexdump.c
index 81b70ed37209..43583cf6accd 100644
--- a/lib/hexdump.c
+++ b/lib/hexdump.c
@@ -210,7 +210,8 @@ EXPORT_SYMBOL(hex_dump_to_buffer);
 
 #ifdef CONFIG_PRINTK
 /**
- * print_hex_dump - print a text hex dump to syslog for a binary blob of data
+ * print_hex_dump_to_cb - print a text hex dump using given callback for a
+ * binary blob of data
  * @level: kernel log level (e.g. KERN_DEBUG)
  * @prefix_str: string to prefix each line with;
  *  caller supplies trailing spaces for alignment if desired
@@ -221,28 +222,18 @@ EXPORT_SYMBOL(hex_dump_to_buffer);
  * @buf: data blob to dump
  * @len: number of bytes in the @buf
  * @ascii: include ASCII after the hex output
+ * @print: the print function, called once for each line
+ * @print_arg: an arbitrary argument to pass to the print function
  *
- * Given a buffer of u8 data, print_hex_dump() prints a hex + ASCII dump
- * to the kernel log at the specified kernel log level, with an optional
- * leading prefix.
- *
- * print_hex_dump() works on one "line" of output at a time, i.e.,
- * 16 or 32 bytes of input data converted to hex + ASCII output.
- * print_hex_dump() iterates over the entire input @buf, breaking it into
- * "line size" chunks to format and print.
- *
- * E.g.:
- *   print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
- *		    16, 1, frame->data, frame->len, true);
+ * This is a low level helper function - normally you want to use
+ * print_hex_dump() or other wrapper around it.
  *
- * Example output using %DUMP_PREFIX_OFFSET and 1-byte mode:
- * 0009ab42: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  @ABCDEFGHIJKLMNO
- * Example output using %DUMP_PREFIX_ADDRESS and 4-byte mode:
- * ffffffff88089af0: 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.
+ * See print_hex_dump() for more details and examples.
  */
-void print_hex_dump(const char *level, const char *prefix_str, int prefix_type,
-		    int rowsize, int groupsize,
-		    const void *buf, size_t len, bool ascii)
+void print_hex_dump_to_cb(const char *level, const char *prefix_str,
+			  int prefix_type, int rowsize, int groupsize,
+			  const void *buf, size_t len, bool ascii,
+			  hex_dump_callback print, void *print_arg)
 {
 	const u8 *ptr = buf;
 	int i, linelen, remaining = len;
@@ -260,18 +251,74 @@ void print_hex_dump(const char *level, const char *prefix_str, int prefix_type,
 
 		switch (prefix_type) {
 		case DUMP_PREFIX_ADDRESS:
-			printk("%s%s%p: %s\n",
-			       level, prefix_str, ptr + i, linebuf);
+			print(level, print_arg, "%s%p: %s\n", prefix_str,
+			      ptr + i, linebuf);
 			break;
 		case DUMP_PREFIX_OFFSET:
-			printk("%s%s%.8x: %s\n", level, prefix_str, i, linebuf);
+			print(level, print_arg, "%s%.8x: %s\n", prefix_str, i,
+			      linebuf);
 			break;
 		default:
-			printk("%s%s%s\n", level, prefix_str, linebuf);
+			print(level, print_arg, "%s%s\n", prefix_str, linebuf);
 			break;
 		}
 	}
 }
+EXPORT_SYMBOL(print_hex_dump_to_cb);
+
+static void print_to_printk(const char *level, void *arg, const char *fmt, ...)
+{
+	va_list args;
+	struct va_format vaf;
+
+	va_start(args, fmt);
+
+	vaf.fmt = fmt;
+	vaf.va = &args;
+
+	printk("%s%pV", level, &vaf);
+
+	va_end(args);
+}
+
+/**
+ * print_hex_dump - print a text hex dump to syslog for a binary blob of data
+ * @level: kernel log level (e.g. KERN_DEBUG)
+ * @prefix_str: string to prefix each line with;
+ *  caller supplies trailing spaces for alignment if desired
+ * @prefix_type: controls whether prefix of an offset, address, or none
+ *  is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE)
+ * @rowsize: number of bytes to print per line; must be 16 or 32
+ * @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
+ * @buf: data blob to dump
+ * @len: number of bytes in the @buf
+ * @ascii: include ASCII after the hex output
+ *
+ * Given a buffer of u8 data, print_hex_dump() prints a hex + ASCII dump
+ * to the kernel log at the specified kernel log level, with an optional
+ * leading prefix.
+ *
+ * print_hex_dump() works on one "line" of output at a time, i.e.,
+ * 16 or 32 bytes of input data converted to hex + ASCII output.
+ * print_hex_dump() iterates over the entire input @buf, breaking it into
+ * "line size" chunks to format and print.
+ *
+ * E.g.:
+ *   print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
+ *		    16, 1, frame->data, frame->len, true);
+ *
+ * Example output using %DUMP_PREFIX_OFFSET and 1-byte mode:
+ * 0009ab42: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f  @ABCDEFGHIJKLMNO
+ * Example output using %DUMP_PREFIX_ADDRESS and 4-byte mode:
+ * ffffffff88089af0: 73727170 77767574 7b7a7978 7f7e7d7c  pqrstuvwxyz{|}~.
+ */
+void print_hex_dump(const char *level, const char *prefix_str, int prefix_type,
+		    int rowsize, int groupsize,
+		    const void *buf, size_t len, bool ascii)
+{
+	print_hex_dump_to_cb(level, prefix_str, prefix_type, rowsize, groupsize,
+			     buf, len, ascii, print_to_printk, NULL);
+}
 EXPORT_SYMBOL(print_hex_dump);
 
 #if !defined(CONFIG_DYNAMIC_DEBUG)
-- 
2.20.1

^ permalink raw reply related

* [PATCH v3 1/4] drm/bridge: sil_sii8620: depend on INPUT instead of selecting it.
From: Ronald Tschalär @ 2019-03-27  1:48 UTC (permalink / raw)
  To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko,
	Sergey Senozhatsky, Steven Rostedt, Greg Kroah-Hartman,
	Rafael J. Wysocki
  Cc: Lukas Wunner, Federico Lorenzi, linux-input, linux-kernel,
	Inki Dae, Andrzej Hajda
In-Reply-To: <20190327014807.7472-1-ronald@innovation.ch>

commit d6abe6df706c66d803e6dd4fe98c1b6b7f125a56 (drm/bridge:
sil_sii8620: do not have a dependency of RC_CORE) added a dependency on
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
future 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, select should only be used for non-visible
symbols. Furthermore almost all other references to INPUT throughout the
kernel config are depends, not selects. Hence this change.

CC: Inki Dae <inki.dae@samsung.com>
CC: Andrzej Hajda <a.hajda@samsung.com>
Signed-off-by: Ronald Tschalär <ronald@innovation.ch>
---
 drivers/gpu/drm/bridge/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 2fee47b0d50b..eabedc83f25c 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -83,9 +83,9 @@ config DRM_PARADE_PS8622
 config DRM_SIL_SII8620
 	tristate "Silicon Image SII8620 HDMI/MHL bridge"
 	depends on OF
+	depends on INPUT
 	select DRM_KMS_HELPER
 	imply EXTCON
-	select INPUT
 	select RC_CORE
 	help
 	  Silicon Image SII8620 HDMI/MHL bridge chip driver.
-- 
2.20.1

^ permalink raw reply related

* [PATCH v3 0/4] Add Apple SPI keyboard and trackpad driver
From: Ronald Tschalär @ 2019-03-27  1:48 UTC (permalink / raw)
  To: Dmitry Torokhov, Henrik Rydberg, Andy Shevchenko,
	Sergey Senozhatsky, Steven Rostedt, Greg Kroah-Hartman,
	Rafael J. Wysocki
  Cc: Lukas Wunner, Federico Lorenzi, linux-input, 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 is just a placeholder for now and is provided in case
somebody wants to compile the driver while it's being reviewed here; the
real patch has been submitted to dri-devel and is being discussed there,
with the intent/hope that I can get an Ack and permission to merge it
through the input subsystem tree here as part of this patch series.

The second and third patches add a new dev_print_hex_dump() helper as
the dev_xxx() analog of print_hex_dump().

The fourth patch finally contains the new applespi driver.

Changes in v3:
  Applied all feedback from review by Andy Shevchenko, including:
  - move dev_print_hex_dump() to driver core
  - clean up keyboard modifier bits testing/modifying
  - remove DEV() macro
  - minor style issues
  The full set of changes to applespi can be viewed at
  https://github.com/roadrunner2/macbook12-spi-driver/ as individual
  commits f832caa..3a6262e in the upstreaming-review branch.

Ronald Tschalär (4):
  drm/bridge: sil_sii8620: depend on INPUT instead of selecting it.
  lib/hexdump.c: factor out generic hexdump formatting for reuse.
  driver core: add dev_print_hex_dump() logging function.
  Input: add Apple SPI keyboard and trackpad driver.

 drivers/base/core.c               |   43 +
 drivers/gpu/drm/bridge/Kconfig    |    2 +-
 drivers/input/keyboard/Kconfig    |   15 +
 drivers/input/keyboard/Makefile   |    1 +
 drivers/input/keyboard/applespi.c | 1988 +++++++++++++++++++++++++++++
 include/linux/device.h            |   15 +
 include/linux/printk.h            |   12 +
 lib/hexdump.c                     |   95 +-
 8 files changed, 2146 insertions(+), 25 deletions(-)
 create mode 100644 drivers/input/keyboard/applespi.c

-- 
2.20.1

^ permalink raw reply

* Re: [PATCH] HID: intel-ish-hid: ISH firmware loader client driver
From: Nick Crews @ 2019-03-27  0:39 UTC (permalink / raw)
  To: Rushikesh S Kadam
  Cc: benjamin.tissoires, jikos, jettrink, gwendal, linux-kernel,
	linux-input, Srinivas Pandruvada
In-Reply-To: <31755c704928710da998353192157ddfd903080c.camel@linux.intel.com>

Hi Rushikesh, I know I've been reviewing this on Chromium, but I have
some more larges-scale design thoughts.

> > diff --git a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
> > b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
> > new file mode 100644
> > index 0000000..85d71d3
> > --- /dev/null
> > +++ b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
> > @@ -0,0 +1,1103 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * ISH-TP client driver for ISH firmware loading
> > + *
> > + * Copyright (c) 2018, Intel Corporation.
> Year 2019.
>
> > + */
> > +
> > +#include <linux/firmware.h>
> > +#include <linux/module.h>
> > +#include <linux/pci.h>
> > +#include <linux/intel-ish-client-if.h>
> > +#include <linux/property.h>
> > +#include <asm/cacheflush.h>
> > +
> > +/* ISH TX/RX ring buffer pool size */
> > +#define LOADER_CL_RX_RING_SIZE                       1
> > +#define LOADER_CL_TX_RING_SIZE                       1
> > +
> > +/*
> > + * ISH Shim firmware loader reserves 4 Kb buffer in SRAM. The buffer
> > is
> > + * used to temporarily hold the data transferred from host to Shim
> > firmware
> > + * loader. Reason for the odd size of 3968 bytes? Each IPC transfer
> > is 128
> > + * bytes (= 4 bytes header + 124 bytes payload). So the 4 Kb buffer
> > can
> > + * hold maximum of 32 IPC transfers, which means we can have a max
> > payload
> > + * of 3968 bytes (= 32 x 124 payload).
> > + */
> > +#define LOADER_SHIM_IPC_BUF_SIZE             3968
> > +
> > +/**
> > + * enum ish_loader_commands -        ISH loader host commands.
> > + * LOADER_CMD_XFER_QUERY     Query the Shim firmware loader for
> > capabilities
> > + * LOADER_CMD_XFER_FRAGMENT  Transfer one firmware image framgment
> > at a
> > + *                           time. The command may be executed
> > multiple
> > + *                           times until the entire firmware image
> > is
> > + *                           downloaded to SRAM.
> > + * LOADER_CMD_START          Start executing the main firmware.
> > + */
> > +enum ish_loader_commands {
> > +     LOADER_CMD_XFER_QUERY = 0,
> > +     LOADER_CMD_XFER_FRAGMENT,
> > +     LOADER_CMD_START,
> > +};
> > +
> > +/* Command bit mask */
> > +#define      CMD_MASK                                GENMASK(6, 0)
> > +#define      IS_RESPONSE                             BIT(7)
> > +
> > +/*
> > + * ISH firmware max delay for one transmit failure is 1 Hz,
> > + * and firmware will retry 2 times, so 3 Hz is used for timeout.
> > + */
> > +#define ISHTP_SEND_TIMEOUT                   (3 * HZ)
> > +
> > +/*
> > + * Loader transfer modes:
> > + *
> > + * LOADER_XFER_MODE_ISHTP mode uses the existing ISH-TP mechanims to
> > + * transfer data. This may use IPC or DMA if supported in firmware.
> > + * The buffer size is limited to 4 Kb by the IPC/ISH-TP protocol for
> > + * both IPC & DMA (legacy).
> > + *
> > + * LOADER_XFER_MODE_DIRECT_DMA - firmware loading is a bit different
> > + * from the sensor data streaming. Here we download a large (300+
> > Kb)
> > + * image directly to ISH SRAM memory. There is limited benefit of
> > + * DMA'ing 300 Kb image in 4 Kb chucks limit. Hence, we introduce
> > + * this "direct dma" mode, where we do not use ISH-TP for DMA, but
> > + * instead manage the DMA directly in kernel driver and Shim
> > firmware
> > + * loader (allocate buf, break in chucks and transfer). This allows
> > + * to overcome 4 Kb limit, and optimize the data flow path in
> > firmware.
> > + */
> > +#define LOADER_XFER_MODE_DIRECT_DMA          BIT(0)
> > +#define LOADER_XFER_MODE_ISHTP                       BIT(1)
> > +
> > +/* ISH Transport Loader client unique GUID */
> > +static const guid_t loader_ishtp_guid =
> > +     GUID_INIT(0xc804d06a, 0x55bd, 0x4ea7,
> > +               0xad, 0xed, 0x1e, 0x31, 0x22, 0x8c, 0x76, 0xdc);
> > +
> > +#define FILENAME_SIZE                                256
> > +
> > +/*
> > + * The firmware loading latency will be minimum if we can DMA the
> > + * entire ISH firmware image in one go. This requires that we
> > allocate
> > + * a large DMA buffer in kernel, which could be problematic on some
> > + * platforms. So here we limit the DMA buf size via a module_param.
> > + * We default to 4 pages, but a customer can set it to higher limit
> > if
> > + * deemed appropriate for his platform.
> > + */
> > +static int dma_buf_size_limit = 4 * PAGE_SIZE;
> > +
> > +/**
> > + * struct loader_msg_hdr - Header for ISH Loader commands.
> > + * @command:         LOADER_CMD* commands. Bit 7 is the response.
> > + * @status:          Command response status. Non 0, is error
> > condition.
> > + *
> > + * This structure is used as header for every command/data
> > sent/received
> > + * between Host driver and ISH Shim firmware loader.
> > + */
> > +struct loader_msg_hdr {
> > +     u8 command;
> > +     u8 reserved[2];
> > +     u8 status;
> > +} __packed;
> > +
> > +struct loader_xfer_query {
> > +     struct loader_msg_hdr hdr;
> > +     u32 image_size;
> > +} __packed;
> > +
> > +struct ish_fw_version {
> > +     u16 major;
> > +     u16 minor;
> > +     u16 hotfix;
> > +     u16 build;
> > +} __packed;
> > +
> > +union loader_version {
> > +     u32 value;
> > +     struct {
> > +             u8 major;
> > +             u8 minor;
> > +             u8 hotfix;
> > +             u8 build;
> > +     };
> > +} __packed;
> > +
> > +struct loader_capability {
> > +     u32 max_fw_image_size;
> > +     u32 xfer_mode;
> > +     u32 max_dma_buf_size; /* only for dma mode, multiples of
> > cacheline */
> > +} __packed;
> > +
> > +struct shim_fw_info {
> > +     struct ish_fw_version ish_fw_version;
> > +     u32 protocol_version;
> > +     union loader_version ldr_version;
> > +     struct loader_capability ldr_capability;
> > +} __packed;
> > +
> > +struct loader_xfer_query_response {
> > +     struct loader_msg_hdr hdr;
> > +     struct shim_fw_info fw_info;
> > +} __packed;
> > +
> > +struct loader_xfer_fragment {
> > +     struct loader_msg_hdr hdr;
> > +     u32 xfer_mode;
> > +     u32 offset;
> > +     u32 size;
> > +     u32 is_last;
> > +} __packed;
> > +
> > +struct loader_xfer_ipc_fragment {
> > +     struct loader_xfer_fragment fragment;
> > +     u8 data[] ____cacheline_aligned; /* variable length payload
> > here */
> > +} __packed;
> > +
> > +struct loader_xfer_dma_fragment {
> > +     struct loader_xfer_fragment fragment;
> > +     u64 ddr_phys_addr;
> > +} __packed;
> > +
> > +struct loader_start {
> > +     struct loader_msg_hdr hdr;
> > +} __packed;
> > +
> > +/**
> > + * struct ishtp_cl_data - Encapsulate per ISH-TP Client Data
> > + * @flag_response    Set true on receiving a firmware  response to
> > host
> > + *                   loader command
> > + * @cmd_resp_wait:   Wait queue for Host firmware loading, where the
> > + *                   client sends message to ISH firmware and wait
> > for
> > + *                   response
> > + * @work_ishtp_reset:        Work queue for reset handling
> > + * @work_fw_load:    Work queue for host firmware loading
> > + * @flag_retry               Flag for indicating host firmware
> > loading should be
> > + *                   retried
> > + * @bad_recv_cnt:    Running count of packets received with error
> > + *
> > + * This structure is used to store data per client
> > + */
> > +struct ishtp_cl_data {
> > +     struct ishtp_cl *loader_ishtp_cl;
> > +     struct ishtp_cl_device *cl_device;
> > +
> > +     /* Completion flags */
> > +     bool flag_response;
> > +
> > +     /* Copy buffer received in firmware "response" here */
> > +     void *response_data;
> > +     size_t response_size;
> > +
> > +     /* Wait queue for ISH firmware message event */
> > +     wait_queue_head_t cmd_resp_wait;
> > +
> > +     struct work_struct work_ishtp_reset;
> > +     struct work_struct work_fw_load;
> > +
> > +     /*
> > +      * In certain failure scenrios, it makes sense to reset the
> > +      * the ISH subsystem and retry Host firmware loading
> > +      * (e.g. bad message packet, ENOMEM, etc.)
> > +      * On the other hand, failures due to protocol mismatch, etc
> > +      * are not recoverable. We do not retry.
> > +      *
> > +      * If set, the flag indictes that we should re-try the
> > particular
> > +      * failure.
> > +      */
> > +     bool flag_retry;
> > +
> > +     /* Statistics */
> > +     unsigned int bad_recv_cnt;
> > +};
> > +
> > +#define IPC_FRAGMENT_DATA_PREAMBLE                           \
> > +     offsetof(struct loader_xfer_ipc_fragment, data)
> > +
> > +#define cl_data_to_dev(client_data) ishtp_device((client_data)-
> > >cl_device)
> > +
> > +/**
> > + * get_firmware_variant() - Gets the filename of firmware image to
> > be
> > + *                   loaded based on platform variant.
> > + * @client_data              Client data instance.
> > + * @filename         Returns firmware filename.
> > + *
> > + * Queries the firmware-name device property string.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int get_firmware_variant(struct ishtp_cl_data *client_data,
> > +                             char *filename)
> > +{
> > +     int rv;
> > +     const char *val;
> > +     struct device *devc = ishtp_get_pci_device(client_data-
> > >cl_device);
> > +
> > +     rv = device_property_read_string(devc, "firmware-name", &val);
> > +     if (rv < 0) {
> > +             dev_err(devc,
> > +                     "Error: ISH firmware-name device property
> > required\n");
> > +             return rv;
> > +     }
> > +     return snprintf(filename, FILENAME_SIZE, "intel/%s", val);
> > +}
> > +
> > +/**
> > + * report_bad_packets() Report bad packets
> > + * @loader_ishtp_cl: Client instance to get stats
> > + * @recv_buf:                Raw received host interface message
> > + *
> > + * Dumps error in case bad packet is received
> > + */
> > +static void report_bad_packet(struct ishtp_cl *loader_ishtp_cl,
> > +                           void *recv_buf)
> > +{
> > +     struct loader_msg_hdr *hdr = recv_buf;
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     client_data->bad_recv_cnt++;
> > +     dev_err(cl_data_to_dev(client_data),
> > +             "BAD packet: command=%02lx is_response=%u status=%02x
> > total_bad=%u\n",
> > +             hdr->command & CMD_MASK,
> > +             hdr->command & IS_RESPONSE ? 1 : 0,
> > +             hdr->status,
> > +             client_data->bad_recv_cnt);
> > +}

I would remove this function. Whenever you call it, you already have
use dev_err() to print the reason for the error. Consider removing
bad_rcv_count too unless you do something with it other than debugging.

At the very least, you call this function when the ISH doesn't return enough
data, but in here you try to access the fields in hdr. This could be accessing
irrelevant/illegal data.

Also a nit: The docstring function name has a naughty trailing s.

> > +
> > +/**
> > + * loader_ish_hw_reset() - Reset ISH HW in bad state
> > + * @loader_ishtp_cl  Client instance to reset
> > + *
> > + * This function resets ISH hardware, which shall reload
> > + * the Shim firmware loader, initiate ISH-TP interface reset,
> > + * re-attach kernel loader driver, and repeat Host
> > + * firmware load.
> > + */
> > +static inline void loader_ish_hw_reset(struct ishtp_cl
> > *loader_ishtp_cl)
> > +{
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     dev_warn(cl_data_to_dev(client_data), "Reset the ISH
> > subsystem\n");
> > +     ish_hw_reset(ishtp_get_ishtp_device(loader_ishtp_cl));
> > +}

Delete this as a function. Before you actually called it in multiple places,
but now i's only called in one place, so just inline it there.

> > +
> > +/**
> > + * loader_cl_send()  Send message from host to firmware
> > + * @client_data:     Client data instance
> > + * @msg                      Message buffer to send
> > + * @msg_size         Size of message
> > + *
> > + * Return: Received buffer size on success, negative error code on
> > failure.
> > + */
> > +static int loader_cl_send(struct ishtp_cl_data *client_data,
> > +                       u8 *msg, size_t msg_size)
> > +{
> > +     int rv;
> > +     size_t data_len;
> > +     struct loader_msg_hdr *in_hdr;
> > +     struct loader_msg_hdr *out_hdr = (struct loader_msg_hdr *)msg;
> > +     struct ishtp_cl *loader_ishtp_cl = client_data-
> > >loader_ishtp_cl;
> > +
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "%s: command=%02lx is_response=%u status=%02x\n",
> > +             __func__,
> > +             out_hdr->command & CMD_MASK,
> > +             out_hdr->command & IS_RESPONSE ? 1 : 0,
> > +             out_hdr->status);
> > +
> > +     client_data->flag_response = false;
> > +     rv = ishtp_cl_send(loader_ishtp_cl, msg, msg_size);
> > +     if (rv < 0) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ishtp_cl_send error %d\n", rv);
> > +             return rv;
> > +     }
> > +
> > +     wait_event_interruptible_timeout(client_data->cmd_resp_wait,
> > +                                      client_data->flag_response,
> > +                                      ISHTP_SEND_TIMEOUT);
> > +     if (!client_data->flag_response) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Timed out for response to command=%02lx",
> > +                     out_hdr->command & CMD_MASK);
> > +             return -ETIMEDOUT;
> > +     }
> > +
> > +     /* All response messages will contain a header */
> > +     data_len = client_data->response_size;
> > +     in_hdr = (struct loader_msg_hdr *)client_data->response_data;

If process_recv() fails then client_data->response_data could be NULL.
This brings up the question of what to do if process_recv() fails. I would think
that you would want it to set something like client_data->response_error
and then you could check for that in here and return it. For instance
right now if the ISH
doesn't return sizeof(struct loader_msg_hdr) bytes then it would be nice to get
-EMSGSIZE returned from here.

> > +
> > +     /* Sanity checks */
> > +     if (!(in_hdr->command & IS_RESPONSE)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Invalid response to command\n");
> > +             return -EIO;
> > +     }
> > +
> > +     if (in_hdr->status) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Loader returned status %d\n",
> > +                     in_hdr->status);
> > +             return -EIO;
> > +     }
> > +
> > +     return data_len;
> > +}

So I think how you've changed this to handle where the data is stored is good,
but it could be better. I don't like how the users of loader_cl_send() need to
remember to kfree(client_data->response data), and that they just implicitly
assume that client_data->response data holds the result. Instead, make the
callers of loader_cl_send() allocate a buffer to hold the result, and then the
allocating and freeing happens in the same function. I think this is a much more
understandable form of memory management.

How about this function turns into:
/**
 * loader_cl_send()  Send message from host to firmware
 * @client_data: Client data instance
 * @in_data: Message buffer to send
 * @in_size: Size of sent data
 * @out_data: Buffer to fill with received data.
 * @out_size: Max number of bytes to place in out_data.
 *
 * Return: Number of bytes placed into out_data, negative error code on failure.
 */
static int loader_cl_send(struct ishtp_cl_data *client_data,
                                        u8 *in_data, size_t in_size,
                                        u8 *out_data, size_t out_size)

{
client_data->response_data = out_data;
client_data->response_size_max = out_size;

Send the command.
Tweak process_recv() so where it does the memcpy() into
client_data->response_data,
add the additional check to make sure it doesn't copy more than
client_data->response_size_max bytes.
Wait for the completion flag.
Continue with the rest.
}

With these suggestions there are now six pieces of info getting
transmitted between
process_recv() and loader_cl_send() via client data:
client_data->cmd_resp_wait
client_data->flag_response
client_data->response_error
client_data->response_size
client_data->response_size_max
client_data->response_data
Consider turning these into:
client_data->response_info->wait_queue
client_data->response_info->data_available // or some better name?
client_data->response_info->error
client_data->response_info->size
client_data->response_info->size_max
client_data->response_info->data
for some encapsulation?

I'm thinking about this more, and basically it seems like we're
writing a library function to
send a command to the ISH and receive a response. All the clients who
use loader_cl_send()
shouldn't know about the client_data->response_info stuff at all. It
almost seems like this
entire functionality should be part of the ISH core? It's really
limiting that ishtp_cl_send() only
allows sending and no receiving! It should?!

> > +
> > +/**
> > + * process_recv() -  Receive and parse incoming packet
> > + * @loader_ishtp_cl: Client instance to get stats
> > + * @rb_in_proc:              ISH received message buffer
> > + *
> > + * Parse the incoming packet. If it is a response packet then it
> > will
> > + * update flag_response and wake up the caller waiting to for the
> > response.
> > + */
> > +static void process_recv(struct ishtp_cl *loader_ishtp_cl,
> > +                      struct ishtp_cl_rb *rb_in_proc)
> > +{
> > +     size_t data_len = rb_in_proc->buf_idx;
> > +     struct loader_msg_hdr *hdr =
> > +             (struct loader_msg_hdr *)rb_in_proc->buffer.data;
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     /*
> > +      * All firmware messages have a header. Check buffer size
> > +      * before accessing elements inside.
> > +      */
> > +     if (data_len < sizeof(struct loader_msg_hdr)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "data size %zu is less than header %zu\n",
> > +                     data_len, sizeof(struct loader_msg_hdr));
> > +             report_bad_packet(client_data->loader_ishtp_cl, hdr);
> > +             goto end_error;
> > +     }
> > +
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "%s: command=%02lx is_response=%u status=%02x\n",
> > +             __func__,
> > +             hdr->command & CMD_MASK,
> > +             hdr->command & IS_RESPONSE ? 1 : 0,
> > +             hdr->status);
> > +
> > +     switch (hdr->command & CMD_MASK) {
> > +     case LOADER_CMD_XFER_QUERY:
> > +     case LOADER_CMD_XFER_FRAGMENT:
> > +     case LOADER_CMD_START:
> > +             /* Sanity check */
> > +             if (client_data->response_data || client_data-
> > >flag_response) {

Following advice above, how about checking
client_data->response_info->data_available instead?
Or with advice above, corrupting old data might not be an issue,
since the destination buffer changes? Also I wouldn't call this a buffer
overrun below, it's a different problem.

> > +                     dev_err(cl_data_to_dev(client_data),
> > +                             "Buffer overrun: previous firmware
> > message not yet processed\n");
> > +                     report_bad_packet(client_data->loader_ishtp_cl,
> > hdr);
> > +                     break;
> > +             }
> > +
> > +             /*
> > +              * Copy the buffer received in firmware response for
> > the
> > +              * calling thread.
> > +              */
> > +             client_data->response_data = kmalloc(data_len,
> > GFP_KERNEL);
> > +             if (!client_data->response_data)
> > +                     break;
> > +
> > +             memcpy(client_data->response_data,
> > +                    rb_in_proc->buffer.data, data_len);
> > +             client_data->response_size = data_len;
> > +
> > +             /* Free the buffer */
> > +             ishtp_cl_io_rb_recycle(rb_in_proc);
> > +             rb_in_proc = NULL;
> > +
> > +             /* Wake the calling thread */
> > +             client_data->flag_response = true;
> > +             wake_up_interruptible(&client_data->cmd_resp_wait);
> > +             break;
> > +
> > +     default:
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Invalid command=%02lx\n",
> > +                     hdr->command & CMD_MASK);
> > +             report_bad_packet(client_data->loader_ishtp_cl, hdr);
> > +     }
> > +
> > +end_error:
> > +     /* Free the buffer if we did not do above */
> > +     if (rb_in_proc)
> > +             ishtp_cl_io_rb_recycle(rb_in_proc);
> > +}
> > +
> > +/**
> > + * loader_cl_event_cb() - bus driver callback for incoming message
> > + * @device:          Pointer to the the ishtp client device for
> > which
> > + *                   this message is targeted
> > + *
> > + * Remove the packet from the list and process the message by
> > calling
> > + * process_recv
> > + */
> > +static void loader_cl_event_cb(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl_rb *rb_in_proc;
> > +     struct ishtp_cl_data *client_data;
> > +     struct ishtp_cl *loader_ishtp_cl =
> > ishtp_get_drvdata(cl_device);
> > +
> > +     client_data = ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     while ((rb_in_proc = ishtp_cl_rx_get_rb(loader_ishtp_cl)) !=
> > NULL) {
> > +             if (!rb_in_proc->buffer.data) {
> > +                     dev_warn(cl_data_to_dev(client_data),
> > +                              "rb_in_proc->buffer.data returned
> > null");

Maybe move this check into process_recv() and then you can set
client_data->response_info->error for it?

> > +                     continue;
> > +             }
> > +
> > +             /* Process the data packet from firmware */
> > +             process_recv(loader_ishtp_cl, rb_in_proc);
> > +     }
> > +}
> > +
> > +/**
> > + * ish_query_loader_prop() -  Query ISH Shim firmware loader
> > + * @client_data:     Client data instance
> > + * @fw:                      Poiner to fw data struct in host memory
> > + *
> > + * This function queries the ISH Shim firmware loader for
> > capabilities.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_query_loader_prop(struct ishtp_cl_data *client_data,
> > +                              const struct firmware *fw,
> > +                              struct shim_fw_info *fw_info)
> > +{
> > +     int rv;
> > +     size_t data_len;
> > +     struct loader_msg_hdr *hdr;
> > +     struct loader_xfer_query ldr_xfer_query;
> > +     struct loader_xfer_query_response *ldr_xfer_query_resp;
> > +
> > +     memset(&ldr_xfer_query, 0, sizeof(ldr_xfer_query));
> > +     ldr_xfer_query.hdr.command = LOADER_CMD_XFER_QUERY;
> > +     ldr_xfer_query.image_size = fw->size;
> > +     rv = loader_cl_send(client_data,
> > +                         (u8 *)&ldr_xfer_query,
> > +                         sizeof(ldr_xfer_query));
> > +     if (rv < 0) {
> > +             client_data->flag_retry = true;
> > +             goto end_error;
> > +     }
> > +
> > +     /* Check buffer size before accessing the elements */
> > +     data_len = client_data->response_size;

Use rv instead of client_data->response_size, we want to minimize weird
unexplainable accesses of the fileds of client_data.

Also consider not using the variable data_len, it doesn't do too much besides
cause some indirection. With the change above it should be obvious
what is going on.

> > +     if (data_len != sizeof(struct loader_xfer_query_response)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "data size %zu is not equal to size of
> > loader_xfer_query_response %zu\n",
> > +                     data_len, sizeof(struct
> > loader_xfer_query_response));
> > +             hdr = (struct loader_msg_hdr *)client_data-
> > >response_data;

Following suggestion above you'll use the

> > +             report_bad_packet(client_data->loader_ishtp_cl, hdr);
> > +             client_data->flag_retry = true;
> > +             rv = -EMSGSIZE;
> > +             goto end_error;
> > +     }
> > +
> > +     /* Save fw_info for use outside this function */
> > +     ldr_xfer_query_resp =
> > +             (struct loader_xfer_query_response *)client_data-
> > >response_data;
> > +     *fw_info = ldr_xfer_query_resp->fw_info;
> > +
> > +     /* Loader firmware properties */
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "ish_fw_version: major=%d minor=%d hotfix=%d build=%d
> > protocol_version=0x%x loader_version=%d\n",
> > +             fw_info->ish_fw_version.major,
> > +             fw_info->ish_fw_version.minor,
> > +             fw_info->ish_fw_version.hotfix,
> > +             fw_info->ish_fw_version.build,
> > +             fw_info->protocol_version,
> > +             fw_info->ldr_version.value);
> > +
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "loader_capability: max_fw_image_size=0x%x xfer_mode=%d
> > max_dma_buf_size=0x%x dma_buf_size_limit=0x%x\n",
> > +             fw_info->ldr_capability.max_fw_image_size,
> > +             fw_info->ldr_capability.xfer_mode,
> > +             fw_info->ldr_capability.max_dma_buf_size,
> > +             dma_buf_size_limit);
> > +
> > +     /* Sanity checks */
> > +     if (fw_info->ldr_capability.max_fw_image_size < fw->size) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ISH firmware size %zu is greater than Shim
> > firmware loader max supported %d\n",
> > +                     fw->size,
> > +                     fw_info->ldr_capability.max_fw_image_size);
> > +             rv = -ENOSPC;
> > +             goto end_error;
> > +     }
> > +
> > +     /* For DMA the buffer size should be multiple of cacheline size
> > */
> > +     if ((fw_info->ldr_capability.xfer_mode &
> > LOADER_XFER_MODE_DIRECT_DMA) &&
> > +         (fw_info->ldr_capability.max_dma_buf_size %
> > L1_CACHE_BYTES)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Shim firmware loader buffer size %d should be
> > multipe of cacheline\n",
> > +                     fw_info->ldr_capability.max_dma_buf_size);
> > +             rv = -EINVAL;
> > +             goto end_error;
> > +     }
> > +
> > +end_error:
> > +     /* Free ISH buffer if not done so in error case */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     return rv;
> > +}
> > +
> > +/**
> > + * ish_fw_xfer_ishtp()       Loads ISH firmware using ishtp
> > interface
> > + * @client_data:     Client data instance
> > + * @fw:                      Pointer to fw data struct in host
> > memory
> > + *
> > + * This function uses ISH-TP to transfer ISH firmware from host to
> > + * ISH SRAM. Lower layers may use IPC or DMA depending on firmware
> > + * support.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_fw_xfer_ishtp(struct ishtp_cl_data *client_data,
> > +                          const struct firmware *fw)
> > +{
> > +     int rv;
> > +     u32 fragment_offset, fragment_size, payload_max_size;
> > +     struct loader_xfer_ipc_fragment *ldr_xfer_ipc_frag;
> > +
> > +     payload_max_size =
> > +             LOADER_SHIM_IPC_BUF_SIZE - IPC_FRAGMENT_DATA_PREAMBLE;
> > +
> > +     ldr_xfer_ipc_frag = kzalloc(LOADER_SHIM_IPC_BUF_SIZE,
> > GFP_KERNEL);
> > +     if (!ldr_xfer_ipc_frag) {
> > +             client_data->flag_retry = true;
> > +             return -ENOMEM;
> > +     }
> > +
> > +     ldr_xfer_ipc_frag->fragment.hdr.command =
> > LOADER_CMD_XFER_FRAGMENT;
> > +     ldr_xfer_ipc_frag->fragment.xfer_mode = LOADER_XFER_MODE_ISHTP;
> > +
> > +     /* Break the firmware image into fragments and send as ISH-TP
> > payload */
> > +     fragment_offset = 0;
> > +     while (fragment_offset < fw->size) {
> > +             if (fragment_offset + payload_max_size < fw->size) {
> > +                     fragment_size = payload_max_size;
> > +                     ldr_xfer_ipc_frag->fragment.is_last = 0;
> > +             } else {
> > +                     fragment_size = fw->size - fragment_offset;
> > +                     ldr_xfer_ipc_frag->fragment.is_last = 1;
> > +             }
> > +
> > +             ldr_xfer_ipc_frag->fragment.offset = fragment_offset;
> > +             ldr_xfer_ipc_frag->fragment.size = fragment_size;
> > +             memcpy(ldr_xfer_ipc_frag->data,
> > +                    &fw->data[fragment_offset],
> > +                    fragment_size);
> > +
> > +             dev_dbg(cl_data_to_dev(client_data),
> > +                     "xfer_mode=ipc offset=0x%08x size=0x%08x
> > is_last=%d\n",
> > +                     ldr_xfer_ipc_frag->fragment.offset,
> > +                     ldr_xfer_ipc_frag->fragment.size,
> > +                     ldr_xfer_ipc_frag->fragment.is_last);
> > +
> > +             rv = loader_cl_send(client_data,
> > +                                 (u8 *)ldr_xfer_ipc_frag,
> > +                                 IPC_FRAGMENT_DATA_PREAMBLE +
> > fragment_size);
> > +             if (rv < 0) {
> > +                     client_data->flag_retry = true;
> > +                     goto end_err_resp_buf_release;
> > +             }
> > +
> > +             /* Free ISH buffer once response is processed */
> > +             kfree(client_data->response_data);
> > +             client_data->response_data = NULL;
> > +
> > +             fragment_offset += fragment_size;
> > +     }
> > +
> > +     kfree(ldr_xfer_ipc_frag);
> > +     return 0;
> > +
> > +end_err_resp_buf_release:
> > +     /* Free ISH buffer if not done already, in error case */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     kfree(ldr_xfer_ipc_frag);
> > +     return rv;
> > +}
> > +
> > +/**
> > + * ish_fw_xfer_direct_dma() - Loads ISH firmware using direct dma
> > + * @client_data:     Client data instance
> > + * @fw:                      Poiner to fw data struct in host memory
> > + *
> > + * Host firmware load is a unique case where we need to download
> > + * a large firmware image (200+ Kb). This function implements
> > + * direct DMA transfer in kernel and ISH firmware. This allows
> > + * us to overcome the ISH-TP 4 Kb limit, and allows us to DMA
> > + * directly to ISH UMA at location of choice.
> > + * Function depends on corresponding support in ISH firmware.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data,
> > +                               const struct firmware *fw,
> > +                               struct shim_fw_info fw_info)
> > +{
> > +     int rv;
> > +     void *dma_buf;
> > +     dma_addr_t dma_buf_phy;
> > +     u32 fragment_offset, fragment_size, payload_max_size;
> > +     struct loader_xfer_dma_fragment ldr_xfer_dma_frag;
> > +     struct device *devc = ishtp_get_pci_device(client_data-
> > >cl_device);
> > +     u32 shim_fw_buf_size =
> > +             fw_info.ldr_capability.max_dma_buf_size;
> > +
> > +     /*
> > +      * payload_max_size should be set to minimum of
> > +      *  (1) Size of firmware to be loaded,
> > +      *  (2) Max DMA buf size supported by Shim firmware,
> > +      *  (3) DMA buffer size limit set by boot_param
> > dma_buf_size_limit.
> > +      */
> > +     payload_max_size = min3(fw->size,
> > +                             (size_t)shim_fw_buf_size,
> > +                             (size_t)dma_buf_size_limit);
> > +
> > +     /*
> > +      * Buffer size should be multiple of cacheline size
> > +      * if it's not, select the previous cacheline boundary.
> > +      */
> > +     payload_max_size &= ~(L1_CACHE_BYTES - 1);
> > +
> > +     dma_buf = kmalloc(payload_max_size, GFP_KERNEL | GFP_DMA32);
> > +     if (!dma_buf) {
> > +             client_data->flag_retry = true;
> > +             return -ENOMEM;
> > +     }
> > +
> > +     dma_buf_phy = dma_map_single(devc, dma_buf, payload_max_size,
> > +                                  DMA_TO_DEVICE);
> > +     if (dma_mapping_error(devc, dma_buf_phy)) {
> > +             dev_err(cl_data_to_dev(client_data), "DMA map
> > failed\n");
> > +             client_data->flag_retry = true;
> > +             rv = -ENOMEM;
> > +             goto end_err_dma_buf_release;
> > +     }
> > +
> > +     ldr_xfer_dma_frag.fragment.hdr.command =
> > LOADER_CMD_XFER_FRAGMENT;
> > +     ldr_xfer_dma_frag.fragment.xfer_mode =
> > LOADER_XFER_MODE_DIRECT_DMA;
> > +     ldr_xfer_dma_frag.ddr_phys_addr = (u64)dma_buf_phy;
> > +
> > +     /* Send the firmware image in chucks of payload_max_size */
> > +     fragment_offset = 0;
> > +     while (fragment_offset < fw->size) {
> > +             if (fragment_offset + payload_max_size < fw->size) {
> > +                     fragment_size = payload_max_size;
> > +                     ldr_xfer_dma_frag.fragment.is_last = 0;
> > +             } else {
> > +                     fragment_size = fw->size - fragment_offset;
> > +                     ldr_xfer_dma_frag.fragment.is_last = 1;
> > +             }
> > +
> > +             ldr_xfer_dma_frag.fragment.offset = fragment_offset;
> > +             ldr_xfer_dma_frag.fragment.size = fragment_size;
> > +             memcpy(dma_buf, &fw->data[fragment_offset],
> > fragment_size);
> > +
> > +             dma_sync_single_for_device(devc, dma_buf_phy,
> > +                                        payload_max_size,
> > +                                        DMA_TO_DEVICE);
> > +
> > +             /*
> > +              * Flush cache here because the
> > dma_sync_single_for_device()
> > +              * does not do for x86.
> > +              */
> > +             clflush_cache_range(dma_buf, payload_max_size);
> > +
> > +             dev_dbg(cl_data_to_dev(client_data),
> > +                     "xfer_mode=dma offset=0x%08x size=0x%x
> > is_last=%d ddr_phys_addr=0x%016llx\n",
> > +                     ldr_xfer_dma_frag.fragment.offset,
> > +                     ldr_xfer_dma_frag.fragment.size,
> > +                     ldr_xfer_dma_frag.fragment.is_last,
> > +                     ldr_xfer_dma_frag.ddr_phys_addr);
> > +
> > +             rv = loader_cl_send(client_data,
> > +                                 (u8 *)&ldr_xfer_dma_frag,
> > +                                 sizeof(ldr_xfer_dma_frag));
> > +             if (rv < 0) {
> > +                     client_data->flag_retry = true;
> > +                     goto end_err_resp_buf_release;
> > +             }
> > +
> > +             /* Free ISH buffer once response is processed */
> > +             kfree(client_data->response_data);
> > +             client_data->response_data = NULL;
> > +
> > +             fragment_offset += fragment_size;
> > +     }
> > +
> > +     dma_unmap_single(devc, dma_buf_phy, payload_max_size,
> > DMA_TO_DEVICE);
> > +     kfree(dma_buf);
> > +     return 0;
> > +
> > +end_err_resp_buf_release:
> > +     /* Free ISH buffer if not done already, in error case */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     dma_unmap_single(devc, dma_buf_phy, payload_max_size,
> > DMA_TO_DEVICE);
> > +end_err_dma_buf_release:
> > +     kfree(dma_buf);
> > +     return rv;
> > +}
> > +
> > +/**
> > + * ish_fw_start()    Start executing ISH main firmware
> > + * @client_data:     client data instance
> > + *
> > + * This function sends message to Shim firmware loader to start
> > + * the execution of ISH main firmware.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_fw_start(struct ishtp_cl_data *client_data)
> > +{
> > +     int rv;
> > +     struct loader_start ldr_start;
> > +
> > +     memset(&ldr_start, 0, sizeof(ldr_start));
> > +     ldr_start.hdr.command = LOADER_CMD_START;
> > +     rv = loader_cl_send(client_data,
> > +                         (u8 *)&ldr_start,
> > +                         sizeof(ldr_start));
> > +
> > +     /* Free ISH buffer once response is processed */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     return rv;
> > +}
> > +
> > +/**
> > + * load_fw_from_host()       Loads ISH firmware from host
> > + * @client_data:     Client data instance
> > + *
> > + * This function loads the ISH firmware to ISH sram and starts
> > execution
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int load_fw_from_host(struct ishtp_cl_data *client_data)
> > +{
> > +     int rv;
> > +     u32 xfer_mode;
> > +     char *filename;
> > +     const struct firmware *fw;
> > +     struct shim_fw_info fw_info;
> > +
> > +     client_data->flag_retry = false;
> > +
> > +     filename = kzalloc(FILENAME_SIZE, GFP_KERNEL);
> > +     if (!filename) {
> > +             rv = -ENOMEM;
> > +             goto end_error;
> > +     }
> > +
> > +     /* Get filename of the ISH firmware to be loaded */
> > +     rv = get_firmware_variant(client_data, filename);
> > +     if (rv < 0)
> > +             goto end_err_filename_buf_release;
> > +
> > +     rv = request_firmware(&fw, filename,
> > cl_data_to_dev(client_data));
> > +     if (rv < 0)
> > +             goto end_err_filename_buf_release;
> > +
> > +     /* Step 1: Query Shim firmware loader properties */
> > +
> > +     rv = ish_query_loader_prop(client_data, fw, &fw_info);
> > +     if (rv < 0)
> > +             goto end_err_fw_release;
> > +
> > +     /* Step 2: Send the main firmware image to be loaded, to ISH
> > sram */
> > +
> > +     xfer_mode = fw_info.ldr_capability.xfer_mode;
> > +     if (xfer_mode & LOADER_XFER_MODE_DIRECT_DMA) {
> > +             rv = ish_fw_xfer_direct_dma(client_data, fw, fw_info);
> > +     } else if (xfer_mode & LOADER_XFER_MODE_ISHTP) {
> > +             rv = ish_fw_xfer_ishtp(client_data, fw);
> > +     } else {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "No transfer mode selected in firmware\n");
> > +             rv = -EINVAL;
> > +     }
> > +     if (rv < 0)
> > +             goto end_err_fw_release;
> > +
> > +     /* Step 3: Start ISH main firmware exeuction */
> > +
> > +     rv = ish_fw_start(client_data);
> > +     if (rv < 0)
> > +             goto end_err_fw_release;
> > +
> > +     release_firmware(fw);
> > +     kfree(filename);
> > +     dev_info(cl_data_to_dev(client_data), "ISH firmware %s
> > loaded\n",
> > +              filename);
> > +     return 0;
> > +
> > +end_err_fw_release:
> > +     release_firmware(fw);
> > +end_err_filename_buf_release:
> > +     kfree(filename);
> > +end_error:
> > +     if (client_data->flag_retry) {
> > +             dev_warn(cl_data_to_dev(client_data),
> > +                      "ISH host firmware load failed %d. Reset ISH &
> > try again..\n",
> > +                      rv);
> > +             loader_ish_hw_reset(client_data->loader_ishtp_cl);

This could just keep failing infinitely, right? Do you want to add
some retry counter,
and after some limit then give up or something? What happens if the ISH firmware
never succeeds in loading?

> > +     } else {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ISH host firmware load failed %d\n", rv);
> > +     }
> > +     return rv;
> > +}

And there were many typos in comments that I saw, comb through them
carefully again.

Cheers,
Nick

^ permalink raw reply

* [PATCH] Input: i8042 - signal wakeup from atkbd/psmouse
From: Dmitry Torokhov @ 2019-03-27  0:28 UTC (permalink / raw)
  To: linux-input
  Cc: Ravi Chandra Sadineni, Rafael J. Wysocki, Shaunak Saha,
	linux-kernel

Instead of signalling wakeup directly from i8042, let psmouse and atkbd
drivers execute basic protocol handling and only then signal wakeup
condition. This solves the issue where we increment wakeup counter
simply because we are getting responses from keyboard/mouse to the
commands we ourselves send to them as part of suspend transition.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/input/keyboard/atkbd.c     | 2 ++
 drivers/input/mouse/psmouse-base.c | 2 ++
 drivers/input/serio/i8042.c        | 3 ---
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index 850bb259c20e..3ad93e3e2f4c 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -401,6 +401,8 @@ static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data,
 		if  (ps2_handle_response(&atkbd->ps2dev, data))
 			goto out;
 
+	pm_wakeup_event(&serio->dev, 0);
+
 	if (!atkbd->enabled)
 		goto out;
 
diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c
index d3ff1fc09af7..94f7ca5ad077 100644
--- a/drivers/input/mouse/psmouse-base.c
+++ b/drivers/input/mouse/psmouse-base.c
@@ -373,6 +373,8 @@ static irqreturn_t psmouse_interrupt(struct serio *serio,
 		if  (ps2_handle_response(&psmouse->ps2dev, data))
 			goto out;
 
+	pm_wakeup_event(&serio->dev, 0);
+
 	if (psmouse->state <= PSMOUSE_RESYNCING)
 		goto out;
 
diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c
index 95a78ccbd847..6462f1798fbb 100644
--- a/drivers/input/serio/i8042.c
+++ b/drivers/input/serio/i8042.c
@@ -573,9 +573,6 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id)
 	port = &i8042_ports[port_no];
 	serio = port->exists ? port->serio : NULL;
 
-	if (irq && serio)
-		pm_wakeup_event(&serio->dev, 0);
-
 	filter_dbg(port->driver_bound, data, "<- i8042 (interrupt, %d, %d%s%s)\n",
 		   port_no, irq,
 		   dfl & SERIO_PARITY ? ", bad parity" : "",
-- 
2.21.0.392.gf8f6787159e-goog


-- 
Dmitry

^ permalink raw reply related

* RE: [PATCH v3] HID: core: move Usage Page concatenation to Main item
From: Junge, Terry @ 2019-03-26 22:43 UTC (permalink / raw)
  To: Nicolas Saenz Julienne, Jiri Kosina, Benjamin Tissoires
  Cc: oneukum@suse.de, linux-input@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <20190326200331.25934-1-nsaenzjulienne@suse.de>

Hi Nicolas,

This patch looks good except for one comment/question below.

Thanks,
Terry

On Tuesday, March 26, 2019 1:04 PM Nicolas Saenz Julienne <nsaenzjulienne@suse.de> wrote:
>
>As seen on some USB wireless keyboards manufactured by Primax, the HID
>parser was using some assumptions that are not always true. In this case it's s
>the fact that, inside the scope of a main item, an Usage Page will always
>precede an Usage.
>
>The spec is not pretty clear as 6.2.2.7 states "Any usage that follows is
>interpreted as a Usage ID and concatenated with the Usage Page".
>While 6.2.2.8 states "When the parser encounters a main item it concatenates
>the last declared Usage Page with a Usage to form a complete usage value."
>Being somewhat contradictory it was decided to match Window's
>implementation, which follows 6.2.2.8.
>
>In summary, the patch moves the Usage Page concatenation from the local
>item parsing function to the main item parsing function.
>
>Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
>---
>
>v2->v3: - Update patch title
>
>v1->v2: - Add usage concatenation to hid_scan_main()
>	- Rework tests in hid-tools, making sure no-one is failing
>
> drivers/hid/hid-core.c | 40 ++++++++++++++++++++++++++++------------
> include/linux/hid.h    |  1 +
> 2 files changed, 29 insertions(+), 12 deletions(-)
>
>diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index
>9993b692598f..40c836ce3248 100644
>--- a/drivers/hid/hid-core.c
>+++ b/drivers/hid/hid-core.c
>@@ -218,13 +218,14 @@ static unsigned hid_lookup_collection(struct
>hid_parser *parser, unsigned type)
>  * Add a usage to the temporary parser table.
>  */
>
>-static int hid_add_usage(struct hid_parser *parser, unsigned usage)
>+static int hid_add_usage(struct hid_parser *parser, unsigned usage,
>+__u8 size)
> {
> 	if (parser->local.usage_index >= HID_MAX_USAGES) {
> 		hid_err(parser->device, "usage index exceeded\n");
> 		return -1;
> 	}
> 	parser->local.usage[parser->local.usage_index] = usage;
>+	parser->local.usage_size[parser->local.usage_index] = size;
> 	parser->local.collection_index[parser->local.usage_index] =
> 		parser->collection_stack_ptr ?
> 		parser->collection_stack[parser->collection_stack_ptr - 1] : 0;
>@@ -486,10 +487,7 @@ static int hid_parser_local(struct hid_parser *parser,
>struct hid_item *item)
> 			return 0;
> 		}
>
>-		if (item->size <= 2)
>-			data = (parser->global.usage_page << 16) + data;
>-
>-		return hid_add_usage(parser, data);
>+		return hid_add_usage(parser, data, item->size);
>
> 	case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:
>
>@@ -498,9 +496,6 @@ static int hid_parser_local(struct hid_parser *parser,
>struct hid_item *item)
> 			return 0;
> 		}
>
>-		if (item->size <= 2)
>-			data = (parser->global.usage_page << 16) + data;
>-
> 		parser->local.usage_minimum = data;
> 		return 0;
>
>@@ -511,9 +506,6 @@ static int hid_parser_local(struct hid_parser *parser,
>struct hid_item *item)
> 			return 0;
> 		}
>
>-		if (item->size <= 2)
>-			data = (parser->global.usage_page << 16) + data;
>-
> 		count = data - parser->local.usage_minimum;
> 		if (count + parser->local.usage_index >= HID_MAX_USAGES) {
> 			/*
>@@ -533,7 +525,7 @@ static int hid_parser_local(struct hid_parser *parser,
>struct hid_item *item)
> 		}
>
> 		for (n = parser->local.usage_minimum; n <= data; n++)
>-			if (hid_add_usage(parser, n)) {
>+			if (hid_add_usage(parser, n, item->size)) {
> 				dbg_hid("hid_add_usage failed\n");
> 				return -1;
> 			}
>@@ -547,6 +539,26 @@ static int hid_parser_local(struct hid_parser *parser,
>struct hid_item *item)
> 	return 0;
> }
>
>+/*
>+ * Concatenate Usage Pages into Usages where relevant:
>+ * As per specification, 6.2.2.8: "When the parser encounters a main
>+item it
>+ * concatenates the last declared Usage Page with a Usage to form a
>+complete
>+ * usage value."
>+ */
>+
>+static void hid_concatenate_usage_page(struct hid_parser *parser) {
>+	unsigned usages;
>+	int i;
>+
>+	usages = max_t(unsigned, parser->local.usage_index,
>+				 parser->global.report_count);

I don't think we need to worry about global.report_count here,
just concatenate for the usages currently in the local queue so could
this be simplified by removing usages and just using local.usage_index?

        for (i = 0; i < local.usage_index; i++)

>+
>+	for (i = 0; i < usages; i++)
>+		if (parser->local.usage_size[i] <= 2)
>+			parser->local.usage[i] += parser->global.usage_page
><< 16; }
>+
> /*
>  * Process a main item.
>  */
>@@ -556,6 +568,8 @@ static int hid_parser_main(struct hid_parser *parser,
>struct hid_item *item)
> 	__u32 data;
> 	int ret;
>
>+	hid_concatenate_usage_page(parser);
>+
> 	data = item_udata(item);
>
> 	switch (item->tag) {
>@@ -765,6 +779,8 @@ static int hid_scan_main(struct hid_parser *parser,
>struct hid_item *item)
> 	__u32 data;
> 	int i;
>
>+	hid_concatenate_usage_page(parser);
>+
> 	data = item_udata(item);
>
> 	switch (item->tag) {
>diff --git a/include/linux/hid.h b/include/linux/hid.h index
>f9707d1dcb58..d1fb4b678873 100644
>--- a/include/linux/hid.h
>+++ b/include/linux/hid.h
>@@ -417,6 +417,7 @@ struct hid_global {
>
> struct hid_local {
> 	unsigned usage[HID_MAX_USAGES]; /* usage array */
>+	__u8 usage_size[HID_MAX_USAGES]; /* usage size array */
> 	unsigned collection_index[HID_MAX_USAGES]; /* collection index
>array */
> 	unsigned usage_index;
> 	unsigned usage_minimum;
>--
>2.21.0

^ permalink raw reply

* Re: [PATCH] HID: intel-ish-hid: ISH firmware loader client driver
From: Jett Rink @ 2019-03-26 21:15 UTC (permalink / raw)
  To: Srinivas Pandruvada
  Cc: Rushikesh S Kadam, benjamin.tissoires, jikos, ncrews,
	Gwendal Grignou, linux-kernel, linux-input
In-Reply-To: <31755c704928710da998353192157ddfd903080c.camel@linux.intel.com>

Tested-by: Jett Rink <jettrink@chromium.org>


On Sun, Mar 24, 2019 at 9:36 AM Srinivas Pandruvada
<srinivas.pandruvada@linux.intel.com> wrote:
>
> On Sat, 2019-03-23 at 16:46 +0530, Rushikesh S Kadam wrote:
> > This driver adds support for loading Intel Integrated
> > Sensor Hub (ISH) firmware from host file system to ISH
> > SRAM and start execution.
> >
> > At power-on, the ISH subsystem shall boot to an interim
> > Shim loader-firmware, which shall expose an ISHTP loader
> > device.
> >
> > The driver implements an ISHTP client that communicates
> > with the Shim ISHTP loader device over the intel-ish-hid
> > stack, to download the main ISH firmware.
> >
> > Signed-off-by: Rushikesh S Kadam <rushikesh.s.kadam@intel.com>
> Anybody in CC list here can add Reviewed-By/Tested-by tag?
>
> One comment below for copyright year.
>
> Thanks,
> Srinivas
> > ---
> > The patches are baselined to hid git tree, branch for-5.2/ish
> >
> https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git/log/?h=for-5.2/ish
> >
> >  drivers/hid/Makefile                        |    1 +
> >  drivers/hid/intel-ish-hid/Kconfig           |   15 +
> >  drivers/hid/intel-ish-hid/Makefile          |    3 +
> >  drivers/hid/intel-ish-hid/ishtp-fw-loader.c | 1103
> > +++++++++++++++++++++++++++
> >  4 files changed, 1122 insertions(+)
> >  create mode 100644 drivers/hid/intel-ish-hid/ishtp-fw-loader.c
> >
> > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> > index 170163b..d8d393e 100644
> > --- a/drivers/hid/Makefile
> > +++ b/drivers/hid/Makefile
> > @@ -134,3 +134,4 @@ obj-$(CONFIG_USB_KBD)             += usbhid/
> >  obj-$(CONFIG_I2C_HID)                += i2c-hid/
> >
> >  obj-$(CONFIG_INTEL_ISH_HID)  += intel-ish-hid/
> > +obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
> > diff --git a/drivers/hid/intel-ish-hid/Kconfig b/drivers/hid/intel-
> > ish-hid/Kconfig
> > index 519e4c8..786adbc 100644
> > --- a/drivers/hid/intel-ish-hid/Kconfig
> > +++ b/drivers/hid/intel-ish-hid/Kconfig
> > @@ -14,4 +14,19 @@ config INTEL_ISH_HID
> >         Broxton and Kaby Lake.
> >
> >         Say Y here if you want to support Intel ISH. If unsure, say
> > N.
> > +
> > +config INTEL_ISH_FIRMWARE_DOWNLOADER
> > +     tristate "Host Firmware Load feature for Intel ISH"
> > +     depends on INTEL_ISH_HID
> > +     depends on X86
> > +     help
> > +       The Integrated Sensor Hub (ISH) enables the kernel to offload
> > +       sensor polling and algorithm processing to a dedicated low
> > power
> > +       processor in the chipset.
> > +
> > +       The Host Firmware Load feature adds support to load the ISH
> > +       firmware from host file system at boot.
> > +
> > +       Say M here if you want to support Host Firmware Loading
> > feature
> > +       for Intel ISH. If unsure, say N.
> >  endmenu
> > diff --git a/drivers/hid/intel-ish-hid/Makefile b/drivers/hid/intel-
> > ish-hid/Makefile
> > index 825b70a..2de97e4 100644
> > --- a/drivers/hid/intel-ish-hid/Makefile
> > +++ b/drivers/hid/intel-ish-hid/Makefile
> > @@ -20,4 +20,7 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ishtp-hid.o
> >  intel-ishtp-hid-objs := ishtp-hid.o
> >  intel-ishtp-hid-objs += ishtp-hid-client.o
> >
> > +obj-$(CONFIG_INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ishtp-loader.o
> > +intel-ishtp-loader-objs += ishtp-fw-loader.o
> > +
> >  ccflags-y += -Idrivers/hid/intel-ish-hid/ishtp
> > diff --git a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
> > b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
> > new file mode 100644
> > index 0000000..85d71d3
> > --- /dev/null
> > +++ b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c
> > @@ -0,0 +1,1103 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * ISH-TP client driver for ISH firmware loading
> > + *
> > + * Copyright (c) 2018, Intel Corporation.
> Year 2019.
>
> > + */
> > +
> > +#include <linux/firmware.h>
> > +#include <linux/module.h>
> > +#include <linux/pci.h>
> > +#include <linux/intel-ish-client-if.h>
> > +#include <linux/property.h>
> > +#include <asm/cacheflush.h>
> > +
> > +/* ISH TX/RX ring buffer pool size */
> > +#define LOADER_CL_RX_RING_SIZE                       1
> > +#define LOADER_CL_TX_RING_SIZE                       1
> > +
> > +/*
> > + * ISH Shim firmware loader reserves 4 Kb buffer in SRAM. The buffer
> > is
> > + * used to temporarily hold the data transferred from host to Shim
> > firmware
> > + * loader. Reason for the odd size of 3968 bytes? Each IPC transfer
> > is 128
> > + * bytes (= 4 bytes header + 124 bytes payload). So the 4 Kb buffer
> > can
> > + * hold maximum of 32 IPC transfers, which means we can have a max
> > payload
> > + * of 3968 bytes (= 32 x 124 payload).
> > + */
> > +#define LOADER_SHIM_IPC_BUF_SIZE             3968
> > +
> > +/**
> > + * enum ish_loader_commands -        ISH loader host commands.
> > + * LOADER_CMD_XFER_QUERY     Query the Shim firmware loader for
> > capabilities
> > + * LOADER_CMD_XFER_FRAGMENT  Transfer one firmware image framgment
> > at a
> > + *                           time. The command may be executed
> > multiple
> > + *                           times until the entire firmware image
> > is
> > + *                           downloaded to SRAM.
> > + * LOADER_CMD_START          Start executing the main firmware.
> > + */
> > +enum ish_loader_commands {
> > +     LOADER_CMD_XFER_QUERY = 0,
> > +     LOADER_CMD_XFER_FRAGMENT,
> > +     LOADER_CMD_START,
> > +};
> > +
> > +/* Command bit mask */
> > +#define      CMD_MASK                                GENMASK(6, 0)
> > +#define      IS_RESPONSE                             BIT(7)
> > +
> > +/*
> > + * ISH firmware max delay for one transmit failure is 1 Hz,
> > + * and firmware will retry 2 times, so 3 Hz is used for timeout.
> > + */
> > +#define ISHTP_SEND_TIMEOUT                   (3 * HZ)
> > +
> > +/*
> > + * Loader transfer modes:
> > + *
> > + * LOADER_XFER_MODE_ISHTP mode uses the existing ISH-TP mechanims to
> > + * transfer data. This may use IPC or DMA if supported in firmware.
> > + * The buffer size is limited to 4 Kb by the IPC/ISH-TP protocol for
> > + * both IPC & DMA (legacy).
> > + *
> > + * LOADER_XFER_MODE_DIRECT_DMA - firmware loading is a bit different
> > + * from the sensor data streaming. Here we download a large (300+
> > Kb)
> > + * image directly to ISH SRAM memory. There is limited benefit of
> > + * DMA'ing 300 Kb image in 4 Kb chucks limit. Hence, we introduce
> > + * this "direct dma" mode, where we do not use ISH-TP for DMA, but
> > + * instead manage the DMA directly in kernel driver and Shim
> > firmware
> > + * loader (allocate buf, break in chucks and transfer). This allows
> > + * to overcome 4 Kb limit, and optimize the data flow path in
> > firmware.
> > + */
> > +#define LOADER_XFER_MODE_DIRECT_DMA          BIT(0)
> > +#define LOADER_XFER_MODE_ISHTP                       BIT(1)
> > +
> > +/* ISH Transport Loader client unique GUID */
> > +static const guid_t loader_ishtp_guid =
> > +     GUID_INIT(0xc804d06a, 0x55bd, 0x4ea7,
> > +               0xad, 0xed, 0x1e, 0x31, 0x22, 0x8c, 0x76, 0xdc);
> > +
> > +#define FILENAME_SIZE                                256
> > +
> > +/*
> > + * The firmware loading latency will be minimum if we can DMA the
> > + * entire ISH firmware image in one go. This requires that we
> > allocate
> > + * a large DMA buffer in kernel, which could be problematic on some
> > + * platforms. So here we limit the DMA buf size via a module_param.
> > + * We default to 4 pages, but a customer can set it to higher limit
> > if
> > + * deemed appropriate for his platform.
> > + */
> > +static int dma_buf_size_limit = 4 * PAGE_SIZE;
> > +
> > +/**
> > + * struct loader_msg_hdr - Header for ISH Loader commands.
> > + * @command:         LOADER_CMD* commands. Bit 7 is the response.
> > + * @status:          Command response status. Non 0, is error
> > condition.
> > + *
> > + * This structure is used as header for every command/data
> > sent/received
> > + * between Host driver and ISH Shim firmware loader.
> > + */
> > +struct loader_msg_hdr {
> > +     u8 command;
> > +     u8 reserved[2];
> > +     u8 status;
> > +} __packed;
> > +
> > +struct loader_xfer_query {
> > +     struct loader_msg_hdr hdr;
> > +     u32 image_size;
> > +} __packed;
> > +
> > +struct ish_fw_version {
> > +     u16 major;
> > +     u16 minor;
> > +     u16 hotfix;
> > +     u16 build;
> > +} __packed;
> > +
> > +union loader_version {
> > +     u32 value;
> > +     struct {
> > +             u8 major;
> > +             u8 minor;
> > +             u8 hotfix;
> > +             u8 build;
> > +     };
> > +} __packed;
> > +
> > +struct loader_capability {
> > +     u32 max_fw_image_size;
> > +     u32 xfer_mode;
> > +     u32 max_dma_buf_size; /* only for dma mode, multiples of
> > cacheline */
> > +} __packed;
> > +
> > +struct shim_fw_info {
> > +     struct ish_fw_version ish_fw_version;
> > +     u32 protocol_version;
> > +     union loader_version ldr_version;
> > +     struct loader_capability ldr_capability;
> > +} __packed;
> > +
> > +struct loader_xfer_query_response {
> > +     struct loader_msg_hdr hdr;
> > +     struct shim_fw_info fw_info;
> > +} __packed;
> > +
> > +struct loader_xfer_fragment {
> > +     struct loader_msg_hdr hdr;
> > +     u32 xfer_mode;
> > +     u32 offset;
> > +     u32 size;
> > +     u32 is_last;
> > +} __packed;
> > +
> > +struct loader_xfer_ipc_fragment {
> > +     struct loader_xfer_fragment fragment;
> > +     u8 data[] ____cacheline_aligned; /* variable length payload
> > here */
> > +} __packed;
> > +
> > +struct loader_xfer_dma_fragment {
> > +     struct loader_xfer_fragment fragment;
> > +     u64 ddr_phys_addr;
> > +} __packed;
> > +
> > +struct loader_start {
> > +     struct loader_msg_hdr hdr;
> > +} __packed;
> > +
> > +/**
> > + * struct ishtp_cl_data - Encapsulate per ISH-TP Client Data
> > + * @flag_response    Set true on receiving a firmware  response to
> > host
> > + *                   loader command
> > + * @cmd_resp_wait:   Wait queue for Host firmware loading, where the
> > + *                   client sends message to ISH firmware and wait
> > for
> > + *                   response
> > + * @work_ishtp_reset:        Work queue for reset handling
> > + * @work_fw_load:    Work queue for host firmware loading
> > + * @flag_retry               Flag for indicating host firmware
> > loading should be
> > + *                   retried
> > + * @bad_recv_cnt:    Running count of packets received with error
> > + *
> > + * This structure is used to store data per client
> > + */
> > +struct ishtp_cl_data {
> > +     struct ishtp_cl *loader_ishtp_cl;
> > +     struct ishtp_cl_device *cl_device;
> > +
> > +     /* Completion flags */
> > +     bool flag_response;
> > +
> > +     /* Copy buffer received in firmware "response" here */
> > +     void *response_data;
> > +     size_t response_size;
> > +
> > +     /* Wait queue for ISH firmware message event */
> > +     wait_queue_head_t cmd_resp_wait;
> > +
> > +     struct work_struct work_ishtp_reset;
> > +     struct work_struct work_fw_load;
> > +
> > +     /*
> > +      * In certain failure scenrios, it makes sense to reset the
> > +      * the ISH subsystem and retry Host firmware loading
> > +      * (e.g. bad message packet, ENOMEM, etc.)
> > +      * On the other hand, failures due to protocol mismatch, etc
> > +      * are not recoverable. We do not retry.
> > +      *
> > +      * If set, the flag indictes that we should re-try the
> > particular
> > +      * failure.
> > +      */
> > +     bool flag_retry;
> > +
> > +     /* Statistics */
> > +     unsigned int bad_recv_cnt;
> > +};
> > +
> > +#define IPC_FRAGMENT_DATA_PREAMBLE                           \
> > +     offsetof(struct loader_xfer_ipc_fragment, data)
> > +
> > +#define cl_data_to_dev(client_data) ishtp_device((client_data)-
> > >cl_device)
> > +
> > +/**
> > + * get_firmware_variant() - Gets the filename of firmware image to
> > be
> > + *                   loaded based on platform variant.
> > + * @client_data              Client data instance.
> > + * @filename         Returns firmware filename.
> > + *
> > + * Queries the firmware-name device property string.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int get_firmware_variant(struct ishtp_cl_data *client_data,
> > +                             char *filename)
> > +{
> > +     int rv;
> > +     const char *val;
> > +     struct device *devc = ishtp_get_pci_device(client_data-
> > >cl_device);
> > +
> > +     rv = device_property_read_string(devc, "firmware-name", &val);
> > +     if (rv < 0) {
> > +             dev_err(devc,
> > +                     "Error: ISH firmware-name device property
> > required\n");
> > +             return rv;
> > +     }
> > +     return snprintf(filename, FILENAME_SIZE, "intel/%s", val);
> > +}
> > +
> > +/**
> > + * report_bad_packets() Report bad packets
> > + * @loader_ishtp_cl: Client instance to get stats
> > + * @recv_buf:                Raw received host interface message
> > + *
> > + * Dumps error in case bad packet is received
> > + */
> > +static void report_bad_packet(struct ishtp_cl *loader_ishtp_cl,
> > +                           void *recv_buf)
> > +{
> > +     struct loader_msg_hdr *hdr = recv_buf;
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     client_data->bad_recv_cnt++;
> > +     dev_err(cl_data_to_dev(client_data),
> > +             "BAD packet: command=%02lx is_response=%u status=%02x
> > total_bad=%u\n",
> > +             hdr->command & CMD_MASK,
> > +             hdr->command & IS_RESPONSE ? 1 : 0,
> > +             hdr->status,
> > +             client_data->bad_recv_cnt);
> > +}
> > +
> > +/**
> > + * loader_ish_hw_reset() - Reset ISH HW in bad state
> > + * @loader_ishtp_cl  Client instance to reset
> > + *
> > + * This function resets ISH hardware, which shall reload
> > + * the Shim firmware loader, initiate ISH-TP interface reset,
> > + * re-attach kernel loader driver, and repeat Host
> > + * firmware load.
> > + */
> > +static inline void loader_ish_hw_reset(struct ishtp_cl
> > *loader_ishtp_cl)
> > +{
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     dev_warn(cl_data_to_dev(client_data), "Reset the ISH
> > subsystem\n");
> > +     ish_hw_reset(ishtp_get_ishtp_device(loader_ishtp_cl));
> > +}
> > +
> > +/**
> > + * loader_cl_send()  Send message from host to firmware
> > + * @client_data:     Client data instance
> > + * @msg                      Message buffer to send
> > + * @msg_size         Size of message
> > + *
> > + * Return: Received buffer size on success, negative error code on
> > failure.
> > + */
> > +static int loader_cl_send(struct ishtp_cl_data *client_data,
> > +                       u8 *msg, size_t msg_size)
> > +{
> > +     int rv;
> > +     size_t data_len;
> > +     struct loader_msg_hdr *in_hdr;
> > +     struct loader_msg_hdr *out_hdr = (struct loader_msg_hdr *)msg;
> > +     struct ishtp_cl *loader_ishtp_cl = client_data-
> > >loader_ishtp_cl;
> > +
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "%s: command=%02lx is_response=%u status=%02x\n",
> > +             __func__,
> > +             out_hdr->command & CMD_MASK,
> > +             out_hdr->command & IS_RESPONSE ? 1 : 0,
> > +             out_hdr->status);
> > +
> > +     client_data->flag_response = false;
> > +     rv = ishtp_cl_send(loader_ishtp_cl, msg, msg_size);
> > +     if (rv < 0) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ishtp_cl_send error %d\n", rv);
> > +             return rv;
> > +     }
> > +
> > +     wait_event_interruptible_timeout(client_data->cmd_resp_wait,
> > +                                      client_data->flag_response,
> > +                                      ISHTP_SEND_TIMEOUT);
> > +     if (!client_data->flag_response) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Timed out for response to command=%02lx",
> > +                     out_hdr->command & CMD_MASK);
> > +             return -ETIMEDOUT;
> > +     }
> > +
> > +     /* All response messages will contain a header */
> > +     data_len = client_data->response_size;
> > +     in_hdr = (struct loader_msg_hdr *)client_data->response_data;
> > +
> > +     /* Sanity checks */
> > +     if (!(in_hdr->command & IS_RESPONSE)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Invalid response to command\n");
> > +             return -EIO;
> > +     }
> > +
> > +     if (in_hdr->status) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Loader returned status %d\n",
> > +                     in_hdr->status);
> > +             return -EIO;
> > +     }
> > +
> > +     return data_len;
> > +}
> > +
> > +/**
> > + * process_recv() -  Receive and parse incoming packet
> > + * @loader_ishtp_cl: Client instance to get stats
> > + * @rb_in_proc:              ISH received message buffer
> > + *
> > + * Parse the incoming packet. If it is a response packet then it
> > will
> > + * update flag_response and wake up the caller waiting to for the
> > response.
> > + */
> > +static void process_recv(struct ishtp_cl *loader_ishtp_cl,
> > +                      struct ishtp_cl_rb *rb_in_proc)
> > +{
> > +     size_t data_len = rb_in_proc->buf_idx;
> > +     struct loader_msg_hdr *hdr =
> > +             (struct loader_msg_hdr *)rb_in_proc->buffer.data;
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     /*
> > +      * All firmware messages have a header. Check buffer size
> > +      * before accessing elements inside.
> > +      */
> > +     if (data_len < sizeof(struct loader_msg_hdr)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "data size %zu is less than header %zu\n",
> > +                     data_len, sizeof(struct loader_msg_hdr));
> > +             report_bad_packet(client_data->loader_ishtp_cl, hdr);
> > +             goto end_error;
> > +     }
> > +
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "%s: command=%02lx is_response=%u status=%02x\n",
> > +             __func__,
> > +             hdr->command & CMD_MASK,
> > +             hdr->command & IS_RESPONSE ? 1 : 0,
> > +             hdr->status);
> > +
> > +     switch (hdr->command & CMD_MASK) {
> > +     case LOADER_CMD_XFER_QUERY:
> > +     case LOADER_CMD_XFER_FRAGMENT:
> > +     case LOADER_CMD_START:
> > +             /* Sanity check */
> > +             if (client_data->response_data || client_data-
> > >flag_response) {
> > +                     dev_err(cl_data_to_dev(client_data),
> > +                             "Buffer overrun: previous firmware
> > message not yet processed\n");
> > +                     report_bad_packet(client_data->loader_ishtp_cl,
> > hdr);
> > +                     break;
> > +             }
> > +
> > +             /*
> > +              * Copy the buffer received in firmware response for
> > the
> > +              * calling thread.
> > +              */
> > +             client_data->response_data = kmalloc(data_len,
> > GFP_KERNEL);
> > +             if (!client_data->response_data)
> > +                     break;
> > +
> > +             memcpy(client_data->response_data,
> > +                    rb_in_proc->buffer.data, data_len);
> > +             client_data->response_size = data_len;
> > +
> > +             /* Free the buffer */
> > +             ishtp_cl_io_rb_recycle(rb_in_proc);
> > +             rb_in_proc = NULL;
> > +
> > +             /* Wake the calling thread */
> > +             client_data->flag_response = true;
> > +             wake_up_interruptible(&client_data->cmd_resp_wait);
> > +             break;
> > +
> > +     default:
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Invalid command=%02lx\n",
> > +                     hdr->command & CMD_MASK);
> > +             report_bad_packet(client_data->loader_ishtp_cl, hdr);
> > +     }
> > +
> > +end_error:
> > +     /* Free the buffer if we did not do above */
> > +     if (rb_in_proc)
> > +             ishtp_cl_io_rb_recycle(rb_in_proc);
> > +}
> > +
> > +/**
> > + * loader_cl_event_cb() - bus driver callback for incoming message
> > + * @device:          Pointer to the the ishtp client device for
> > which
> > + *                   this message is targeted
> > + *
> > + * Remove the packet from the list and process the message by
> > calling
> > + * process_recv
> > + */
> > +static void loader_cl_event_cb(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl_rb *rb_in_proc;
> > +     struct ishtp_cl_data *client_data;
> > +     struct ishtp_cl *loader_ishtp_cl =
> > ishtp_get_drvdata(cl_device);
> > +
> > +     client_data = ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     while ((rb_in_proc = ishtp_cl_rx_get_rb(loader_ishtp_cl)) !=
> > NULL) {
> > +             if (!rb_in_proc->buffer.data) {
> > +                     dev_warn(cl_data_to_dev(client_data),
> > +                              "rb_in_proc->buffer.data returned
> > null");
> > +                     continue;
> > +             }
> > +
> > +             /* Process the data packet from firmware */
> > +             process_recv(loader_ishtp_cl, rb_in_proc);
> > +     }
> > +}
> > +
> > +/**
> > + * ish_query_loader_prop() -  Query ISH Shim firmware loader
> > + * @client_data:     Client data instance
> > + * @fw:                      Poiner to fw data struct in host memory
> > + *
> > + * This function queries the ISH Shim firmware loader for
> > capabilities.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_query_loader_prop(struct ishtp_cl_data *client_data,
> > +                              const struct firmware *fw,
> > +                              struct shim_fw_info *fw_info)
> > +{
> > +     int rv;
> > +     size_t data_len;
> > +     struct loader_msg_hdr *hdr;
> > +     struct loader_xfer_query ldr_xfer_query;
> > +     struct loader_xfer_query_response *ldr_xfer_query_resp;
> > +
> > +     memset(&ldr_xfer_query, 0, sizeof(ldr_xfer_query));
> > +     ldr_xfer_query.hdr.command = LOADER_CMD_XFER_QUERY;
> > +     ldr_xfer_query.image_size = fw->size;
> > +     rv = loader_cl_send(client_data,
> > +                         (u8 *)&ldr_xfer_query,
> > +                         sizeof(ldr_xfer_query));
> > +     if (rv < 0) {
> > +             client_data->flag_retry = true;
> > +             goto end_error;
> > +     }
> > +
> > +     /* Check buffer size before accessing the elements */
> > +     data_len = client_data->response_size;
> > +     if (data_len != sizeof(struct loader_xfer_query_response)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "data size %zu is not equal to size of
> > loader_xfer_query_response %zu\n",
> > +                     data_len, sizeof(struct
> > loader_xfer_query_response));
> > +             hdr = (struct loader_msg_hdr *)client_data-
> > >response_data;
> > +             report_bad_packet(client_data->loader_ishtp_cl, hdr);
> > +             client_data->flag_retry = true;
> > +             rv = -EMSGSIZE;
> > +             goto end_error;
> > +     }
> > +
> > +     /* Save fw_info for use outside this function */
> > +     ldr_xfer_query_resp =
> > +             (struct loader_xfer_query_response *)client_data-
> > >response_data;
> > +     *fw_info = ldr_xfer_query_resp->fw_info;
> > +
> > +     /* Loader firmware properties */
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "ish_fw_version: major=%d minor=%d hotfix=%d build=%d
> > protocol_version=0x%x loader_version=%d\n",
> > +             fw_info->ish_fw_version.major,
> > +             fw_info->ish_fw_version.minor,
> > +             fw_info->ish_fw_version.hotfix,
> > +             fw_info->ish_fw_version.build,
> > +             fw_info->protocol_version,
> > +             fw_info->ldr_version.value);
> > +
> > +     dev_dbg(cl_data_to_dev(client_data),
> > +             "loader_capability: max_fw_image_size=0x%x xfer_mode=%d
> > max_dma_buf_size=0x%x dma_buf_size_limit=0x%x\n",
> > +             fw_info->ldr_capability.max_fw_image_size,
> > +             fw_info->ldr_capability.xfer_mode,
> > +             fw_info->ldr_capability.max_dma_buf_size,
> > +             dma_buf_size_limit);
> > +
> > +     /* Sanity checks */
> > +     if (fw_info->ldr_capability.max_fw_image_size < fw->size) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ISH firmware size %zu is greater than Shim
> > firmware loader max supported %d\n",
> > +                     fw->size,
> > +                     fw_info->ldr_capability.max_fw_image_size);
> > +             rv = -ENOSPC;
> > +             goto end_error;
> > +     }
> > +
> > +     /* For DMA the buffer size should be multiple of cacheline size
> > */
> > +     if ((fw_info->ldr_capability.xfer_mode &
> > LOADER_XFER_MODE_DIRECT_DMA) &&
> > +         (fw_info->ldr_capability.max_dma_buf_size %
> > L1_CACHE_BYTES)) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "Shim firmware loader buffer size %d should be
> > multipe of cacheline\n",
> > +                     fw_info->ldr_capability.max_dma_buf_size);
> > +             rv = -EINVAL;
> > +             goto end_error;
> > +     }
> > +
> > +end_error:
> > +     /* Free ISH buffer if not done so in error case */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     return rv;
> > +}
> > +
> > +/**
> > + * ish_fw_xfer_ishtp()       Loads ISH firmware using ishtp
> > interface
> > + * @client_data:     Client data instance
> > + * @fw:                      Pointer to fw data struct in host
> > memory
> > + *
> > + * This function uses ISH-TP to transfer ISH firmware from host to
> > + * ISH SRAM. Lower layers may use IPC or DMA depending on firmware
> > + * support.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_fw_xfer_ishtp(struct ishtp_cl_data *client_data,
> > +                          const struct firmware *fw)
> > +{
> > +     int rv;
> > +     u32 fragment_offset, fragment_size, payload_max_size;
> > +     struct loader_xfer_ipc_fragment *ldr_xfer_ipc_frag;
> > +
> > +     payload_max_size =
> > +             LOADER_SHIM_IPC_BUF_SIZE - IPC_FRAGMENT_DATA_PREAMBLE;
> > +
> > +     ldr_xfer_ipc_frag = kzalloc(LOADER_SHIM_IPC_BUF_SIZE,
> > GFP_KERNEL);
> > +     if (!ldr_xfer_ipc_frag) {
> > +             client_data->flag_retry = true;
> > +             return -ENOMEM;
> > +     }
> > +
> > +     ldr_xfer_ipc_frag->fragment.hdr.command =
> > LOADER_CMD_XFER_FRAGMENT;
> > +     ldr_xfer_ipc_frag->fragment.xfer_mode = LOADER_XFER_MODE_ISHTP;
> > +
> > +     /* Break the firmware image into fragments and send as ISH-TP
> > payload */
> > +     fragment_offset = 0;
> > +     while (fragment_offset < fw->size) {
> > +             if (fragment_offset + payload_max_size < fw->size) {
> > +                     fragment_size = payload_max_size;
> > +                     ldr_xfer_ipc_frag->fragment.is_last = 0;
> > +             } else {
> > +                     fragment_size = fw->size - fragment_offset;
> > +                     ldr_xfer_ipc_frag->fragment.is_last = 1;
> > +             }
> > +
> > +             ldr_xfer_ipc_frag->fragment.offset = fragment_offset;
> > +             ldr_xfer_ipc_frag->fragment.size = fragment_size;
> > +             memcpy(ldr_xfer_ipc_frag->data,
> > +                    &fw->data[fragment_offset],
> > +                    fragment_size);
> > +
> > +             dev_dbg(cl_data_to_dev(client_data),
> > +                     "xfer_mode=ipc offset=0x%08x size=0x%08x
> > is_last=%d\n",
> > +                     ldr_xfer_ipc_frag->fragment.offset,
> > +                     ldr_xfer_ipc_frag->fragment.size,
> > +                     ldr_xfer_ipc_frag->fragment.is_last);
> > +
> > +             rv = loader_cl_send(client_data,
> > +                                 (u8 *)ldr_xfer_ipc_frag,
> > +                                 IPC_FRAGMENT_DATA_PREAMBLE +
> > fragment_size);
> > +             if (rv < 0) {
> > +                     client_data->flag_retry = true;
> > +                     goto end_err_resp_buf_release;
> > +             }
> > +
> > +             /* Free ISH buffer once response is processed */
> > +             kfree(client_data->response_data);
> > +             client_data->response_data = NULL;
> > +
> > +             fragment_offset += fragment_size;
> > +     }
> > +
> > +     kfree(ldr_xfer_ipc_frag);
> > +     return 0;
> > +
> > +end_err_resp_buf_release:
> > +     /* Free ISH buffer if not done already, in error case */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     kfree(ldr_xfer_ipc_frag);
> > +     return rv;
> > +}
> > +
> > +/**
> > + * ish_fw_xfer_direct_dma() - Loads ISH firmware using direct dma
> > + * @client_data:     Client data instance
> > + * @fw:                      Poiner to fw data struct in host memory
> > + *
> > + * Host firmware load is a unique case where we need to download
> > + * a large firmware image (200+ Kb). This function implements
> > + * direct DMA transfer in kernel and ISH firmware. This allows
> > + * us to overcome the ISH-TP 4 Kb limit, and allows us to DMA
> > + * directly to ISH UMA at location of choice.
> > + * Function depends on corresponding support in ISH firmware.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data,
> > +                               const struct firmware *fw,
> > +                               struct shim_fw_info fw_info)
> > +{
> > +     int rv;
> > +     void *dma_buf;
> > +     dma_addr_t dma_buf_phy;
> > +     u32 fragment_offset, fragment_size, payload_max_size;
> > +     struct loader_xfer_dma_fragment ldr_xfer_dma_frag;
> > +     struct device *devc = ishtp_get_pci_device(client_data-
> > >cl_device);
> > +     u32 shim_fw_buf_size =
> > +             fw_info.ldr_capability.max_dma_buf_size;
> > +
> > +     /*
> > +      * payload_max_size should be set to minimum of
> > +      *  (1) Size of firmware to be loaded,
> > +      *  (2) Max DMA buf size supported by Shim firmware,
> > +      *  (3) DMA buffer size limit set by boot_param
> > dma_buf_size_limit.
> > +      */
> > +     payload_max_size = min3(fw->size,
> > +                             (size_t)shim_fw_buf_size,
> > +                             (size_t)dma_buf_size_limit);
> > +
> > +     /*
> > +      * Buffer size should be multiple of cacheline size
> > +      * if it's not, select the previous cacheline boundary.
> > +      */
> > +     payload_max_size &= ~(L1_CACHE_BYTES - 1);
> > +
> > +     dma_buf = kmalloc(payload_max_size, GFP_KERNEL | GFP_DMA32);
> > +     if (!dma_buf) {
> > +             client_data->flag_retry = true;
> > +             return -ENOMEM;
> > +     }
> > +
> > +     dma_buf_phy = dma_map_single(devc, dma_buf, payload_max_size,
> > +                                  DMA_TO_DEVICE);
> > +     if (dma_mapping_error(devc, dma_buf_phy)) {
> > +             dev_err(cl_data_to_dev(client_data), "DMA map
> > failed\n");
> > +             client_data->flag_retry = true;
> > +             rv = -ENOMEM;
> > +             goto end_err_dma_buf_release;
> > +     }
> > +
> > +     ldr_xfer_dma_frag.fragment.hdr.command =
> > LOADER_CMD_XFER_FRAGMENT;
> > +     ldr_xfer_dma_frag.fragment.xfer_mode =
> > LOADER_XFER_MODE_DIRECT_DMA;
> > +     ldr_xfer_dma_frag.ddr_phys_addr = (u64)dma_buf_phy;
> > +
> > +     /* Send the firmware image in chucks of payload_max_size */
> > +     fragment_offset = 0;
> > +     while (fragment_offset < fw->size) {
> > +             if (fragment_offset + payload_max_size < fw->size) {
> > +                     fragment_size = payload_max_size;
> > +                     ldr_xfer_dma_frag.fragment.is_last = 0;
> > +             } else {
> > +                     fragment_size = fw->size - fragment_offset;
> > +                     ldr_xfer_dma_frag.fragment.is_last = 1;
> > +             }
> > +
> > +             ldr_xfer_dma_frag.fragment.offset = fragment_offset;
> > +             ldr_xfer_dma_frag.fragment.size = fragment_size;
> > +             memcpy(dma_buf, &fw->data[fragment_offset],
> > fragment_size);
> > +
> > +             dma_sync_single_for_device(devc, dma_buf_phy,
> > +                                        payload_max_size,
> > +                                        DMA_TO_DEVICE);
> > +
> > +             /*
> > +              * Flush cache here because the
> > dma_sync_single_for_device()
> > +              * does not do for x86.
> > +              */
> > +             clflush_cache_range(dma_buf, payload_max_size);
> > +
> > +             dev_dbg(cl_data_to_dev(client_data),
> > +                     "xfer_mode=dma offset=0x%08x size=0x%x
> > is_last=%d ddr_phys_addr=0x%016llx\n",
> > +                     ldr_xfer_dma_frag.fragment.offset,
> > +                     ldr_xfer_dma_frag.fragment.size,
> > +                     ldr_xfer_dma_frag.fragment.is_last,
> > +                     ldr_xfer_dma_frag.ddr_phys_addr);
> > +
> > +             rv = loader_cl_send(client_data,
> > +                                 (u8 *)&ldr_xfer_dma_frag,
> > +                                 sizeof(ldr_xfer_dma_frag));
> > +             if (rv < 0) {
> > +                     client_data->flag_retry = true;
> > +                     goto end_err_resp_buf_release;
> > +             }
> > +
> > +             /* Free ISH buffer once response is processed */
> > +             kfree(client_data->response_data);
> > +             client_data->response_data = NULL;
> > +
> > +             fragment_offset += fragment_size;
> > +     }
> > +
> > +     dma_unmap_single(devc, dma_buf_phy, payload_max_size,
> > DMA_TO_DEVICE);
> > +     kfree(dma_buf);
> > +     return 0;
> > +
> > +end_err_resp_buf_release:
> > +     /* Free ISH buffer if not done already, in error case */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     dma_unmap_single(devc, dma_buf_phy, payload_max_size,
> > DMA_TO_DEVICE);
> > +end_err_dma_buf_release:
> > +     kfree(dma_buf);
> > +     return rv;
> > +}
> > +
> > +/**
> > + * ish_fw_start()    Start executing ISH main firmware
> > + * @client_data:     client data instance
> > + *
> > + * This function sends message to Shim firmware loader to start
> > + * the execution of ISH main firmware.
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int ish_fw_start(struct ishtp_cl_data *client_data)
> > +{
> > +     int rv;
> > +     struct loader_start ldr_start;
> > +
> > +     memset(&ldr_start, 0, sizeof(ldr_start));
> > +     ldr_start.hdr.command = LOADER_CMD_START;
> > +     rv = loader_cl_send(client_data,
> > +                         (u8 *)&ldr_start,
> > +                         sizeof(ldr_start));
> > +
> > +     /* Free ISH buffer once response is processed */
> > +     kfree(client_data->response_data);
> > +     client_data->response_data = NULL;
> > +     return rv;
> > +}
> > +
> > +/**
> > + * load_fw_from_host()       Loads ISH firmware from host
> > + * @client_data:     Client data instance
> > + *
> > + * This function loads the ISH firmware to ISH sram and starts
> > execution
> > + *
> > + * Return: 0 for success, negative error code for failure.
> > + */
> > +static int load_fw_from_host(struct ishtp_cl_data *client_data)
> > +{
> > +     int rv;
> > +     u32 xfer_mode;
> > +     char *filename;
> > +     const struct firmware *fw;
> > +     struct shim_fw_info fw_info;
> > +
> > +     client_data->flag_retry = false;
> > +
> > +     filename = kzalloc(FILENAME_SIZE, GFP_KERNEL);
> > +     if (!filename) {
> > +             rv = -ENOMEM;
> > +             goto end_error;
> > +     }
> > +
> > +     /* Get filename of the ISH firmware to be loaded */
> > +     rv = get_firmware_variant(client_data, filename);
> > +     if (rv < 0)
> > +             goto end_err_filename_buf_release;
> > +
> > +     rv = request_firmware(&fw, filename,
> > cl_data_to_dev(client_data));
> > +     if (rv < 0)
> > +             goto end_err_filename_buf_release;
> > +
> > +     /* Step 1: Query Shim firmware loader properties */
> > +
> > +     rv = ish_query_loader_prop(client_data, fw, &fw_info);
> > +     if (rv < 0)
> > +             goto end_err_fw_release;
> > +
> > +     /* Step 2: Send the main firmware image to be loaded, to ISH
> > sram */
> > +
> > +     xfer_mode = fw_info.ldr_capability.xfer_mode;
> > +     if (xfer_mode & LOADER_XFER_MODE_DIRECT_DMA) {
> > +             rv = ish_fw_xfer_direct_dma(client_data, fw, fw_info);
> > +     } else if (xfer_mode & LOADER_XFER_MODE_ISHTP) {
> > +             rv = ish_fw_xfer_ishtp(client_data, fw);
> > +     } else {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "No transfer mode selected in firmware\n");
> > +             rv = -EINVAL;
> > +     }
> > +     if (rv < 0)
> > +             goto end_err_fw_release;
> > +
> > +     /* Step 3: Start ISH main firmware exeuction */
> > +
> > +     rv = ish_fw_start(client_data);
> > +     if (rv < 0)
> > +             goto end_err_fw_release;
> > +
> > +     release_firmware(fw);
> > +     kfree(filename);
> > +     dev_info(cl_data_to_dev(client_data), "ISH firmware %s
> > loaded\n",
> > +              filename);
> > +     return 0;
> > +
> > +end_err_fw_release:
> > +     release_firmware(fw);
> > +end_err_filename_buf_release:
> > +     kfree(filename);
> > +end_error:
> > +     if (client_data->flag_retry) {
> > +             dev_warn(cl_data_to_dev(client_data),
> > +                      "ISH host firmware load failed %d. Reset ISH &
> > try again..\n",
> > +                      rv);
> > +             loader_ish_hw_reset(client_data->loader_ishtp_cl);
> > +     } else {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ISH host firmware load failed %d\n", rv);
> > +     }
> > +     return rv;
> > +}
> > +
> > +static void load_fw_from_host_handler(struct work_struct *work)
> > +{
> > +     struct ishtp_cl_data *client_data;
> > +
> > +     client_data = container_of(work, struct ishtp_cl_data,
> > +                                work_fw_load);
> > +     load_fw_from_host(client_data);
> > +}
> > +
> > +/**
> > + * loader_init() -   Init function for ISH-TP client
> > + * @loader_ishtp_cl: ISH-TP client instance
> > + * @reset:           true if called for init after reset
> > + *
> > + * Return: 0 for success, negative error code for failure
> > + */
> > +static int loader_init(struct ishtp_cl *loader_ishtp_cl, int reset)
> > +{
> > +     int rv;
> > +     struct ishtp_fw_client *fw_client;
> > +     struct ishtp_cl_data *client_data =
> > +             ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     dev_dbg(cl_data_to_dev(client_data), "reset flag: %d\n",
> > reset);
> > +
> > +     rv = ishtp_cl_link(loader_ishtp_cl);
> > +     if (rv < 0) {
> > +             dev_err(cl_data_to_dev(client_data), "ishtp_cl_link
> > failed\n");
> > +             return rv;
> > +     }
> > +
> > +     /* Connect to firmware client */
> > +     ishtp_set_tx_ring_size(loader_ishtp_cl,
> > LOADER_CL_TX_RING_SIZE);
> > +     ishtp_set_rx_ring_size(loader_ishtp_cl,
> > LOADER_CL_RX_RING_SIZE);
> > +
> > +     fw_client =
> > +             ishtp_fw_cl_get_client(ishtp_get_ishtp_device(loader_is
> > htp_cl),
> > +                                    &loader_ishtp_guid);
> > +     if (!fw_client) {
> > +             dev_err(cl_data_to_dev(client_data),
> > +                     "ISH client uuid not found\n");
> > +             rv = -ENOENT;
> > +             goto err_cl_unlink;
> > +     }
> > +
> > +     ishtp_cl_set_fw_client_id(loader_ishtp_cl,
> > +                               ishtp_get_fw_client_id(fw_client));
> > +     ishtp_set_connection_state(loader_ishtp_cl,
> > ISHTP_CL_CONNECTING);
> > +
> > +     rv = ishtp_cl_connect(loader_ishtp_cl);
> > +     if (rv < 0) {
> > +             dev_err(cl_data_to_dev(client_data), "Client connect
> > fail\n");
> > +             goto err_cl_unlink;
> > +     }
> > +
> > +     dev_dbg(cl_data_to_dev(client_data), "Client connected\n");
> > +
> > +     ishtp_register_event_cb(client_data->cl_device,
> > loader_cl_event_cb);
> > +
> > +     return 0;
> > +
> > +err_cl_unlink:
> > +     ishtp_cl_unlink(loader_ishtp_cl);
> > +     return rv;
> > +}
> > +
> > +static void loader_deinit(struct ishtp_cl *loader_ishtp_cl)
> > +{
> > +     ishtp_set_connection_state(loader_ishtp_cl,
> > ISHTP_CL_DISCONNECTING);
> > +     ishtp_cl_disconnect(loader_ishtp_cl);
> > +     ishtp_cl_unlink(loader_ishtp_cl);
> > +     ishtp_cl_flush_queues(loader_ishtp_cl);
> > +
> > +     /* Disband and free all Tx and Rx client-level rings */
> > +     ishtp_cl_free(loader_ishtp_cl);
> > +}
> > +
> > +static void reset_handler(struct work_struct *work)
> > +{
> > +     int rv;
> > +     struct ishtp_cl_data *client_data;
> > +     struct ishtp_cl *loader_ishtp_cl;
> > +     struct ishtp_cl_device *cl_device;
> > +
> > +     client_data = container_of(work, struct ishtp_cl_data,
> > +                                work_ishtp_reset);
> > +
> > +     loader_ishtp_cl = client_data->loader_ishtp_cl;
> > +     cl_device = client_data->cl_device;
> > +
> > +     /* Unlink, flush queues & start again */
> > +     ishtp_cl_unlink(loader_ishtp_cl);
> > +     ishtp_cl_flush_queues(loader_ishtp_cl);
> > +     ishtp_cl_free(loader_ishtp_cl);
> > +
> > +     loader_ishtp_cl = ishtp_cl_allocate(cl_device);
> > +     if (!loader_ishtp_cl)
> > +             return;
> > +
> > +     ishtp_set_drvdata(cl_device, loader_ishtp_cl);
> > +     ishtp_set_client_data(loader_ishtp_cl, client_data);
> > +     client_data->loader_ishtp_cl = loader_ishtp_cl;
> > +     client_data->cl_device = cl_device;
> > +
> > +     rv = loader_init(loader_ishtp_cl, 1);
> > +     if (rv < 0) {
> > +             dev_err(ishtp_device(cl_device), "Reset Failed\n");
> > +             return;
> > +     }
> > +
> > +     /* ISH firmware loading from host */
> > +     load_fw_from_host(client_data);
> > +}
> > +
> > +/**
> > + * loader_ishtp_cl_probe() - ISH-TP client driver probe
> > + * @cl_device:               ISH-TP client device instance
> > + *
> > + * This function gets called on device create on ISH-TP bus
> > + *
> > + * Return: 0 for success, negative error code for failure
> > + */
> > +static int loader_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl *loader_ishtp_cl;
> > +     struct ishtp_cl_data *client_data;
> > +     int rv;
> > +
> > +     client_data = devm_kzalloc(ishtp_device(cl_device),
> > +                                sizeof(*client_data),
> > +                                GFP_KERNEL);
> > +     if (!client_data)
> > +             return -ENOMEM;
> > +
> > +     loader_ishtp_cl = ishtp_cl_allocate(cl_device);
> > +     if (!loader_ishtp_cl)
> > +             return -ENOMEM;
> > +
> > +     ishtp_set_drvdata(cl_device, loader_ishtp_cl);
> > +     ishtp_set_client_data(loader_ishtp_cl, client_data);
> > +     client_data->loader_ishtp_cl = loader_ishtp_cl;
> > +     client_data->cl_device = cl_device;
> > +
> > +     init_waitqueue_head(&client_data->cmd_resp_wait);
> > +
> > +     INIT_WORK(&client_data->work_ishtp_reset,
> > +               reset_handler);
> > +     INIT_WORK(&client_data->work_fw_load,
> > +               load_fw_from_host_handler);
> > +
> > +     rv = loader_init(loader_ishtp_cl, 0);
> > +     if (rv < 0) {
> > +             ishtp_cl_free(loader_ishtp_cl);
> > +             return rv;
> > +     }
> > +     ishtp_get_device(cl_device);
> > +
> > +     /* ISH firmware loading from host */
> > +     schedule_work(&client_data->work_fw_load);
> > +
> > +     return 0;
> > +}
> > +
> > +/**
> > + * loader_ishtp_cl_remove() - ISH-TP client driver remove
> > + * @cl_device:               ISH-TP client device instance
> > + *
> > + * This function gets called on device remove on ISH-TP bus
> > + *
> > + * Return: 0
> > + */
> > +static int loader_ishtp_cl_remove(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl_data *client_data;
> > +     struct ishtp_cl *loader_ishtp_cl =
> > ishtp_get_drvdata(cl_device);
> > +
> > +     client_data = ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     /*
> > +      * The sequence of the following two cancel_work_sync() is
> > +      * important. The work_fw_load can in turn schedue
> > +      * work_ishtp_reset, so first cancel work_fw_load then
> > +      * cancel work_ishtp_reset.
> > +      */
> > +     cancel_work_sync(&client_data->work_fw_load);
> > +     cancel_work_sync(&client_data->work_ishtp_reset);
> > +     loader_deinit(loader_ishtp_cl);
> > +     ishtp_put_device(cl_device);
> > +
> > +     return 0;
> > +}
> > +
> > +/**
> > + * loader_ishtp_cl_reset() - ISH-TP client driver reset
> > + * @cl_device:               ISH-TP client device instance
> > + *
> > + * This function gets called on device reset on ISH-TP bus
> > + *
> > + * Return: 0
> > + */
> > +static int loader_ishtp_cl_reset(struct ishtp_cl_device *cl_device)
> > +{
> > +     struct ishtp_cl_data *client_data;
> > +     struct ishtp_cl *loader_ishtp_cl =
> > ishtp_get_drvdata(cl_device);
> > +
> > +     client_data = ishtp_get_client_data(loader_ishtp_cl);
> > +
> > +     schedule_work(&client_data->work_ishtp_reset);
> > +
> > +     return 0;
> > +}
> > +
> > +static struct ishtp_cl_driver        loader_ishtp_cl_driver = {
> > +     .name = "ish-loader",
> > +     .guid = &loader_ishtp_guid,
> > +     .probe = loader_ishtp_cl_probe,
> > +     .remove = loader_ishtp_cl_remove,
> > +     .reset = loader_ishtp_cl_reset,
> > +};
> > +
> > +static int __init ish_loader_init(void)
> > +{
> > +     return ishtp_cl_driver_register(&loader_ishtp_cl_driver,
> > THIS_MODULE);
> > +}
> > +
> > +static void __exit ish_loader_exit(void)
> > +{
> > +     ishtp_cl_driver_unregister(&loader_ishtp_cl_driver);
> > +}
> > +
> > +late_initcall(ish_loader_init);
> > +module_exit(ish_loader_exit);
> > +
> > +module_param(dma_buf_size_limit, int, 0644);
> > +MODULE_PARM_DESC(dma_buf_size_limit, "Limit the DMA buf size to this
> > value in bytes");
> > +
> > +MODULE_DESCRIPTION("ISH ISH-TP Host firmware Loader Client Driver");
> > +MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>");
> > +
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_ALIAS("ishtp:*");
>

^ permalink raw reply

* [PATCH v3] HID: core: move Usage Page concatenation to Main item
From: Nicolas Saenz Julienne @ 2019-03-26 20:03 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: oneukum, Terry.Junge, Nicolas Saenz Julienne, linux-input,
	linux-kernel

As seen on some USB wireless keyboards manufactured by Primax, the HID
parser was using some assumptions that are not always true. In this case
it's s the fact that, inside the scope of a main item, an Usage Page
will always precede an Usage.

The spec is not pretty clear as 6.2.2.7 states "Any usage that follows
is interpreted as a Usage ID and concatenated with the Usage Page".
While 6.2.2.8 states "When the parser encounters a main item it
concatenates the last declared Usage Page with a Usage to form a
complete usage value." Being somewhat contradictory it was decided to
match Window's implementation, which follows 6.2.2.8.

In summary, the patch moves the Usage Page concatenation from the local
item parsing function to the main item parsing function.

Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
---

v2->v3: - Update patch title

v1->v2: - Add usage concatenation to hid_scan_main()
	- Rework tests in hid-tools, making sure no-one is failing

 drivers/hid/hid-core.c | 40 ++++++++++++++++++++++++++++------------
 include/linux/hid.h    |  1 +
 2 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 9993b692598f..40c836ce3248 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -218,13 +218,14 @@ static unsigned hid_lookup_collection(struct hid_parser *parser, unsigned type)
  * Add a usage to the temporary parser table.
  */
 
-static int hid_add_usage(struct hid_parser *parser, unsigned usage)
+static int hid_add_usage(struct hid_parser *parser, unsigned usage, __u8 size)
 {
 	if (parser->local.usage_index >= HID_MAX_USAGES) {
 		hid_err(parser->device, "usage index exceeded\n");
 		return -1;
 	}
 	parser->local.usage[parser->local.usage_index] = usage;
+	parser->local.usage_size[parser->local.usage_index] = size;
 	parser->local.collection_index[parser->local.usage_index] =
 		parser->collection_stack_ptr ?
 		parser->collection_stack[parser->collection_stack_ptr - 1] : 0;
@@ -486,10 +487,7 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 			return 0;
 		}
 
-		if (item->size <= 2)
-			data = (parser->global.usage_page << 16) + data;
-
-		return hid_add_usage(parser, data);
+		return hid_add_usage(parser, data, item->size);
 
 	case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:
 
@@ -498,9 +496,6 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 			return 0;
 		}
 
-		if (item->size <= 2)
-			data = (parser->global.usage_page << 16) + data;
-
 		parser->local.usage_minimum = data;
 		return 0;
 
@@ -511,9 +506,6 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 			return 0;
 		}
 
-		if (item->size <= 2)
-			data = (parser->global.usage_page << 16) + data;
-
 		count = data - parser->local.usage_minimum;
 		if (count + parser->local.usage_index >= HID_MAX_USAGES) {
 			/*
@@ -533,7 +525,7 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 		}
 
 		for (n = parser->local.usage_minimum; n <= data; n++)
-			if (hid_add_usage(parser, n)) {
+			if (hid_add_usage(parser, n, item->size)) {
 				dbg_hid("hid_add_usage failed\n");
 				return -1;
 			}
@@ -547,6 +539,26 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 	return 0;
 }
 
+/*
+ * Concatenate Usage Pages into Usages where relevant:
+ * As per specification, 6.2.2.8: "When the parser encounters a main item it
+ * concatenates the last declared Usage Page with a Usage to form a complete
+ * usage value."
+ */
+
+static void hid_concatenate_usage_page(struct hid_parser *parser)
+{
+	unsigned usages;
+	int i;
+
+	usages = max_t(unsigned, parser->local.usage_index,
+				 parser->global.report_count);
+
+	for (i = 0; i < usages; i++)
+		if (parser->local.usage_size[i] <= 2)
+			parser->local.usage[i] += parser->global.usage_page << 16;
+}
+
 /*
  * Process a main item.
  */
@@ -556,6 +568,8 @@ static int hid_parser_main(struct hid_parser *parser, struct hid_item *item)
 	__u32 data;
 	int ret;
 
+	hid_concatenate_usage_page(parser);
+
 	data = item_udata(item);
 
 	switch (item->tag) {
@@ -765,6 +779,8 @@ static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
 	__u32 data;
 	int i;
 
+	hid_concatenate_usage_page(parser);
+
 	data = item_udata(item);
 
 	switch (item->tag) {
diff --git a/include/linux/hid.h b/include/linux/hid.h
index f9707d1dcb58..d1fb4b678873 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -417,6 +417,7 @@ struct hid_global {
 
 struct hid_local {
 	unsigned usage[HID_MAX_USAGES]; /* usage array */
+	__u8 usage_size[HID_MAX_USAGES]; /* usage size array */
 	unsigned collection_index[HID_MAX_USAGES]; /* collection index array */
 	unsigned usage_index;
 	unsigned usage_minimum;
-- 
2.21.0

^ permalink raw reply related

* [PATCH v2] HID: core: move Usage Page concatenation to hid_parser_main()
From: Nicolas Saenz Julienne @ 2019-03-26 19:59 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: oneukum, Terry.Junge, Nicolas Saenz Julienne, linux-input,
	linux-kernel

As seen on some USB wireless keyboards manufactured by Primax, the HID
parser was using some assumptions that are not always true. In this case
it's s the fact that, inside the scope of a main item, an Usage Page
will always precede an Usage.

The spec is not pretty clear as 6.2.2.7 states "Any usage that follows
is interpreted as a Usage ID and concatenated with the Usage Page".
While 6.2.2.8 states "When the parser encounters a main item it
concatenates the last declared Usage Page with a Usage to form a
complete usage value." Being somewhat contradictory it was decided to
match Window's implementation, which follows 6.2.2.8.

In summary, the patch moves the Usage Page concatenation from the local
item parsing function to the main item parsing function.

Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
---

v1->v2: - Add usage concatenation to hid_scan_main()
	- Rework tests in hid-tools, making sure no-one is failing

 drivers/hid/hid-core.c | 40 ++++++++++++++++++++++++++++------------
 include/linux/hid.h    |  1 +
 2 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 9993b692598f..40c836ce3248 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -218,13 +218,14 @@ static unsigned hid_lookup_collection(struct hid_parser *parser, unsigned type)
  * Add a usage to the temporary parser table.
  */
 
-static int hid_add_usage(struct hid_parser *parser, unsigned usage)
+static int hid_add_usage(struct hid_parser *parser, unsigned usage, __u8 size)
 {
 	if (parser->local.usage_index >= HID_MAX_USAGES) {
 		hid_err(parser->device, "usage index exceeded\n");
 		return -1;
 	}
 	parser->local.usage[parser->local.usage_index] = usage;
+	parser->local.usage_size[parser->local.usage_index] = size;
 	parser->local.collection_index[parser->local.usage_index] =
 		parser->collection_stack_ptr ?
 		parser->collection_stack[parser->collection_stack_ptr - 1] : 0;
@@ -486,10 +487,7 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 			return 0;
 		}
 
-		if (item->size <= 2)
-			data = (parser->global.usage_page << 16) + data;
-
-		return hid_add_usage(parser, data);
+		return hid_add_usage(parser, data, item->size);
 
 	case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:
 
@@ -498,9 +496,6 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 			return 0;
 		}
 
-		if (item->size <= 2)
-			data = (parser->global.usage_page << 16) + data;
-
 		parser->local.usage_minimum = data;
 		return 0;
 
@@ -511,9 +506,6 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 			return 0;
 		}
 
-		if (item->size <= 2)
-			data = (parser->global.usage_page << 16) + data;
-
 		count = data - parser->local.usage_minimum;
 		if (count + parser->local.usage_index >= HID_MAX_USAGES) {
 			/*
@@ -533,7 +525,7 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 		}
 
 		for (n = parser->local.usage_minimum; n <= data; n++)
-			if (hid_add_usage(parser, n)) {
+			if (hid_add_usage(parser, n, item->size)) {
 				dbg_hid("hid_add_usage failed\n");
 				return -1;
 			}
@@ -547,6 +539,26 @@ static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
 	return 0;
 }
 
+/*
+ * Concatenate Usage Pages into Usages where relevant:
+ * As per specification, 6.2.2.8: "When the parser encounters a main item it
+ * concatenates the last declared Usage Page with a Usage to form a complete
+ * usage value."
+ */
+
+static void hid_concatenate_usage_page(struct hid_parser *parser)
+{
+	unsigned usages;
+	int i;
+
+	usages = max_t(unsigned, parser->local.usage_index,
+				 parser->global.report_count);
+
+	for (i = 0; i < usages; i++)
+		if (parser->local.usage_size[i] <= 2)
+			parser->local.usage[i] += parser->global.usage_page << 16;
+}
+
 /*
  * Process a main item.
  */
@@ -556,6 +568,8 @@ static int hid_parser_main(struct hid_parser *parser, struct hid_item *item)
 	__u32 data;
 	int ret;
 
+	hid_concatenate_usage_page(parser);
+
 	data = item_udata(item);
 
 	switch (item->tag) {
@@ -765,6 +779,8 @@ static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
 	__u32 data;
 	int i;
 
+	hid_concatenate_usage_page(parser);
+
 	data = item_udata(item);
 
 	switch (item->tag) {
diff --git a/include/linux/hid.h b/include/linux/hid.h
index f9707d1dcb58..d1fb4b678873 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -417,6 +417,7 @@ struct hid_global {
 
 struct hid_local {
 	unsigned usage[HID_MAX_USAGES]; /* usage array */
+	__u8 usage_size[HID_MAX_USAGES]; /* usage size array */
 	unsigned collection_index[HID_MAX_USAGES]; /* collection index array */
 	unsigned usage_index;
 	unsigned usage_minimum;
-- 
2.21.0

^ permalink raw reply related

* Re: [RESEND PATCH v6 02/11] dt-bindings: power: supply: add DT bindings for max77650
From: Bartosz Golaszewski @ 2019-03-26 17:34 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Lee Jones, Sebastian Reichel, Liam Girdwood,
	Greg Kroah-Hartman, Linux Kernel Mailing List,
	open list:GPIO SUBSYSTEM, devicetree, Linux Input,
	Linux LED Subsystem, Linux PM list, Bartosz Golaszewski
In-Reply-To: <20190322090038.GE27015@amd>

pt., 22 mar 2019 o 10:00 Pavel Machek <pavel@ucw.cz> napisał(a):
>
> On Mon 2019-03-18 18:40:31, Bartosz Golaszewski wrote:
> > From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> >
> > Add the DT binding document for the battery charger module of max77650.
> >
> > Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> > ---
> >  .../power/supply/max77650-charger.txt         | 27 +++++++++++++++++++
> >  1 file changed, 27 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/power/supply/max77650-charger.txt
> >
> > diff --git a/Documentation/devicetree/bindings/power/supply/max77650-charger.txt b/Documentation/devicetree/bindings/power/supply/max77650-charger.txt
> > new file mode 100644
> > index 000000000000..d25c95369616
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/power/supply/max77650-charger.txt
> > @@ -0,0 +1,27 @@
> > +Battery charger driver for MAX77650 PMIC from Maxim Integrated.
> > +
> > +This module is part of the MAX77650 MFD device. For more details
> > +see Documentation/devicetree/bindings/mfd/max77650.txt.
> > +
> > +The charger is represented as a sub-node of the PMIC node on the device tree.
> > +
> > +Required properties:
> > +--------------------
> > +- compatible:                Must be "maxim,max77650-charger"
> > +
> > +Optional properties:
> > +--------------------
> > +- min-microvolt:     Minimum CHGIN regulation voltage (in microvolts). Must be
> > +                     one of: 4000000, 4100000, 4200000, 4300000, 4400000,
> > +                     4500000, 4600000, 4700000.
>
> Probably needs "max," prefix. And .. what does this mean? Will charger
> shutdown if input is less than this?
>

The charger will enter the undervoltage lockout state and stop
charging, this is explained in the manual, so I don't think the
bindings are the right place to add this information.

Bart

> > +- curr-lim-microamp: CHGIN input current limit (in microamps). Must be one of:
> > +                     95000, 190000, 285000, 380000, 475000.
>
> "current-limit-microamp", I guess. And probably "max,current-limit-microamp".
>
> --
> (english) http://www.livejournal.com/~pavelmachek
> (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html

^ permalink raw reply

* [PATCH v7 11/11] MAINTAINERS: add an entry for max77650 mfd driver
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

I plan on extending this set of drivers so add myself as maintainer.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 MAINTAINERS | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 3e5a5d263f29..b32fe859c341 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9407,6 +9407,20 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/sound/max9860.txt
 F:	sound/soc/codecs/max9860.*
 
+MAXIM MAX77650 PMIC MFD DRIVER
+M:	Bartosz Golaszewski <bgolaszewski@baylibre.com>
+L:	linux-kernel@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/*/*max77650.txt
+F:	Documentation/devicetree/bindings/*/max77650*.txt
+F:	include/linux/mfd/max77650.h
+F:	drivers/mfd/max77650.c
+F:	drivers/regulator/max77650-regulator.c
+F:	drivers/power/supply/max77650-charger.c
+F:	drivers/input/misc/max77650-onkey.c
+F:	drivers/leds/leds-max77650.c
+F:	drivers/gpio/gpio-max77650.c
+
 MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER
 M:	Javier Martinez Canillas <javier@dowhile0.org>
 L:	linux-kernel@vger.kernel.org
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 10/11] input: max77650: add onkey support
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add support for the push- and slide-button events for max77650.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Acked-by: Pavel Machek <pavel@ucw.cz>
---
 drivers/input/misc/Kconfig          |   9 +++
 drivers/input/misc/Makefile         |   1 +
 drivers/input/misc/max77650-onkey.c | 121 ++++++++++++++++++++++++++++
 3 files changed, 131 insertions(+)
 create mode 100644 drivers/input/misc/max77650-onkey.c

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index e15ed1bb8558..85bc675eecd3 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -190,6 +190,15 @@ config INPUT_M68K_BEEP
 	tristate "M68k Beeper support"
 	depends on M68K
 
+config INPUT_MAX77650_ONKEY
+	tristate "Maxim MAX77650 ONKEY support"
+	depends on MFD_MAX77650
+	help
+	  Support the ONKEY of the MAX77650 PMIC as an input device.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called max77650-onkey.
+
 config INPUT_MAX77693_HAPTIC
 	tristate "MAXIM MAX77693/MAX77843 haptic controller support"
 	depends on (MFD_MAX77693 || MFD_MAX77843) && PWM
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index b936c5b1d4ac..ffd72161c79b 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -43,6 +43,7 @@ obj-$(CONFIG_INPUT_IXP4XX_BEEPER)	+= ixp4xx-beeper.o
 obj-$(CONFIG_INPUT_KEYSPAN_REMOTE)	+= keyspan_remote.o
 obj-$(CONFIG_INPUT_KXTJ9)		+= kxtj9.o
 obj-$(CONFIG_INPUT_M68K_BEEP)		+= m68kspkr.o
+obj-$(CONFIG_INPUT_MAX77650_ONKEY)	+= max77650-onkey.o
 obj-$(CONFIG_INPUT_MAX77693_HAPTIC)	+= max77693-haptic.o
 obj-$(CONFIG_INPUT_MAX8925_ONKEY)	+= max8925_onkey.o
 obj-$(CONFIG_INPUT_MAX8997_HAPTIC)	+= max8997_haptic.o
diff --git a/drivers/input/misc/max77650-onkey.c b/drivers/input/misc/max77650-onkey.c
new file mode 100644
index 000000000000..fbf6caab7217
--- /dev/null
+++ b/drivers/input/misc/max77650-onkey.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// ONKEY driver for MAXIM 77650/77651 charger/power-supply.
+
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77650_ONKEY_MODE_MASK	BIT(3)
+#define MAX77650_ONKEY_MODE_PUSH	0x00
+#define MAX77650_ONKEY_MODE_SLIDE	BIT(3)
+
+struct max77650_onkey {
+	struct input_dev *input;
+	unsigned int code;
+};
+
+static irqreturn_t max77650_onkey_falling(int irq, void *data)
+{
+	struct max77650_onkey *onkey = data;
+
+	input_report_key(onkey->input, onkey->code, 0);
+	input_sync(onkey->input);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t max77650_onkey_rising(int irq, void *data)
+{
+	struct max77650_onkey *onkey = data;
+
+	input_report_key(onkey->input, onkey->code, 1);
+	input_sync(onkey->input);
+
+	return IRQ_HANDLED;
+}
+
+static int max77650_onkey_probe(struct platform_device *pdev)
+{
+	int irq_r, irq_f, error, mode;
+	struct max77650_onkey *onkey;
+	struct device *dev, *parent;
+	struct regmap *map;
+	unsigned int type;
+
+	dev = &pdev->dev;
+	parent = dev->parent;
+
+	map = dev_get_regmap(parent, NULL);
+	if (!map)
+		return -ENODEV;
+
+	onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL);
+	if (!onkey)
+		return -ENOMEM;
+
+	error = device_property_read_u32(dev, "linux,code", &onkey->code);
+	if (error)
+		onkey->code = KEY_POWER;
+
+	if (device_property_read_bool(dev, "maxim,onkey-slide")) {
+		mode = MAX77650_ONKEY_MODE_SLIDE;
+		type = EV_SW;
+	} else {
+		mode = MAX77650_ONKEY_MODE_PUSH;
+		type = EV_KEY;
+	}
+
+	error = regmap_update_bits(map, MAX77650_REG_CNFG_GLBL,
+				   MAX77650_ONKEY_MODE_MASK, mode);
+	if (error)
+		return error;
+
+	irq_f = platform_get_irq_byname(pdev, "nEN_F");
+	if (irq_f < 0)
+		return irq_f;
+
+	irq_r = platform_get_irq_byname(pdev, "nEN_R");
+	if (irq_r < 0)
+		return irq_r;
+
+	onkey->input = devm_input_allocate_device(dev);
+	if (!onkey->input)
+		return -ENOMEM;
+
+	onkey->input->name = "max77650_onkey";
+	onkey->input->phys = "max77650_onkey/input0";
+	onkey->input->id.bustype = BUS_I2C;
+	input_set_capability(onkey->input, type, onkey->code);
+
+	error = devm_request_any_context_irq(dev, irq_f, max77650_onkey_falling,
+					     IRQF_ONESHOT, "onkey-down", onkey);
+	if (error < 0)
+		return error;
+
+	error = devm_request_any_context_irq(dev, irq_r, max77650_onkey_rising,
+					     IRQF_ONESHOT, "onkey-up", onkey);
+	if (error < 0)
+		return error;
+
+	return input_register_device(onkey->input);
+}
+
+static struct platform_driver max77650_onkey_driver = {
+	.driver = {
+		.name = "max77650-onkey",
+	},
+	.probe = max77650_onkey_probe,
+};
+module_platform_driver(max77650_onkey_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 ONKEY driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 09/11] leds: max77650: add LEDs support
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

This adds basic support for LEDs for the max77650 PMIC. The device has
three current sinks for driving LEDs.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Acked-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
---
 drivers/leds/Kconfig         |   6 ++
 drivers/leds/Makefile        |   1 +
 drivers/leds/leds-max77650.c | 147 +++++++++++++++++++++++++++++++++++
 3 files changed, 154 insertions(+)
 create mode 100644 drivers/leds/leds-max77650.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a72f97fca57b..d8c70cc6a714 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -608,6 +608,12 @@ config LEDS_TLC591XX
 	  This option enables support for Texas Instruments TLC59108
 	  and TLC59116 LED controllers.
 
+config LEDS_MAX77650
+	tristate "LED support for Maxim MAX77650 PMIC"
+	depends on LEDS_CLASS && MFD_MAX77650
+	help
+	  LEDs driver for MAX77650 family of PMICs from Maxim Integrated.
+
 config LEDS_MAX77693
 	tristate "LED support for MAX77693 Flash"
 	depends on LEDS_CLASS_FLASH
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 4c1b0054f379..f48b2404dbb7 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_LEDS_MC13783)		+= leds-mc13783.o
 obj-$(CONFIG_LEDS_NS2)			+= leds-ns2.o
 obj-$(CONFIG_LEDS_NETXBIG)		+= leds-netxbig.o
 obj-$(CONFIG_LEDS_ASIC3)		+= leds-asic3.o
+obj-$(CONFIG_LEDS_MAX77650)		+= leds-max77650.o
 obj-$(CONFIG_LEDS_MAX77693)		+= leds-max77693.o
 obj-$(CONFIG_LEDS_MAX8997)		+= leds-max8997.o
 obj-$(CONFIG_LEDS_LM355x)		+= leds-lm355x.o
diff --git a/drivers/leds/leds-max77650.c b/drivers/leds/leds-max77650.c
new file mode 100644
index 000000000000..6b74ce9cac12
--- /dev/null
+++ b/drivers/leds/leds-max77650.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// LED driver for MAXIM 77650/77651 charger/power-supply.
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77650_LED_NUM_LEDS		3
+
+#define MAX77650_LED_A_BASE		0x40
+#define MAX77650_LED_B_BASE		0x43
+
+#define MAX77650_LED_BR_MASK		GENMASK(4, 0)
+#define MAX77650_LED_EN_MASK		GENMASK(7, 6)
+
+#define MAX77650_LED_MAX_BRIGHTNESS	MAX77650_LED_BR_MASK
+
+/* Enable EN_LED_MSTR. */
+#define MAX77650_LED_TOP_DEFAULT	BIT(0)
+
+#define MAX77650_LED_ENABLE		GENMASK(7, 6)
+#define MAX77650_LED_DISABLE		0x00
+
+#define MAX77650_LED_A_DEFAULT		MAX77650_LED_DISABLE
+/* 100% on duty */
+#define MAX77650_LED_B_DEFAULT		GENMASK(3, 0)
+
+struct max77650_led {
+	struct led_classdev cdev;
+	struct regmap *map;
+	unsigned int regA;
+	unsigned int regB;
+};
+
+static struct max77650_led *max77650_to_led(struct led_classdev *cdev)
+{
+	return container_of(cdev, struct max77650_led, cdev);
+}
+
+static int max77650_led_brightness_set(struct led_classdev *cdev,
+				       enum led_brightness brightness)
+{
+	struct max77650_led *led = max77650_to_led(cdev);
+	int val, mask;
+
+	mask = MAX77650_LED_BR_MASK | MAX77650_LED_EN_MASK;
+
+	if (brightness == LED_OFF)
+		val = MAX77650_LED_DISABLE;
+	else
+		val = MAX77650_LED_ENABLE | brightness;
+
+	return regmap_update_bits(led->map, led->regA, mask, val);
+}
+
+static int max77650_led_probe(struct platform_device *pdev)
+{
+	struct device_node *of_node, *child;
+	struct max77650_led *leds, *led;
+	struct device *parent;
+	struct device *dev;
+	struct regmap *map;
+	const char *label;
+	int rv, num_leds;
+	u32 reg;
+
+	dev = &pdev->dev;
+	parent = dev->parent;
+	of_node = dev->of_node;
+
+	if (!of_node)
+		return -ENODEV;
+
+	leds = devm_kcalloc(dev, sizeof(*leds),
+			    MAX77650_LED_NUM_LEDS, GFP_KERNEL);
+	if (!leds)
+		return -ENOMEM;
+
+	map = dev_get_regmap(dev->parent, NULL);
+	if (!map)
+		return -ENODEV;
+
+	num_leds = of_get_child_count(of_node);
+	if (!num_leds || num_leds > MAX77650_LED_NUM_LEDS)
+		return -ENODEV;
+
+	for_each_child_of_node(of_node, child) {
+		rv = of_property_read_u32(child, "reg", &reg);
+		if (rv || reg >= MAX77650_LED_NUM_LEDS)
+			return -EINVAL;
+
+		led = &leds[reg];
+		led->map = map;
+		led->regA = MAX77650_LED_A_BASE + reg;
+		led->regB = MAX77650_LED_B_BASE + reg;
+		led->cdev.brightness_set_blocking = max77650_led_brightness_set;
+		led->cdev.max_brightness = MAX77650_LED_MAX_BRIGHTNESS;
+
+		label = of_get_property(child, "label", NULL);
+		if (!label) {
+			led->cdev.name = "max77650::";
+		} else {
+			led->cdev.name = devm_kasprintf(dev, GFP_KERNEL,
+							"max77650:%s", label);
+			if (!led->cdev.name)
+				return -ENOMEM;
+		}
+
+		of_property_read_string(child, "linux,default-trigger",
+					&led->cdev.default_trigger);
+
+		rv = devm_of_led_classdev_register(dev, child, &led->cdev);
+		if (rv)
+			return rv;
+
+		rv = regmap_write(map, led->regA, MAX77650_LED_A_DEFAULT);
+		if (rv)
+			return rv;
+
+		rv = regmap_write(map, led->regB, MAX77650_LED_B_DEFAULT);
+		if (rv)
+			return rv;
+	}
+
+	return regmap_write(map,
+			    MAX77650_REG_CNFG_LED_TOP,
+			    MAX77650_LED_TOP_DEFAULT);
+}
+
+static struct platform_driver max77650_led_driver = {
+	.driver = {
+		.name = "max77650-led",
+	},
+	.probe = max77650_led_probe,
+};
+module_platform_driver(max77650_led_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 LED driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 08/11] gpio: max77650: add GPIO support
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add GPIO support for max77650 mfd device. This PMIC exposes a single
GPIO line.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
---
 drivers/gpio/Kconfig         |   7 ++
 drivers/gpio/Makefile        |   1 +
 drivers/gpio/gpio-max77650.c | 190 +++++++++++++++++++++++++++++++++++
 3 files changed, 198 insertions(+)
 create mode 100644 drivers/gpio/gpio-max77650.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 3f50526a771f..c4f912104440 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1112,6 +1112,13 @@ config GPIO_MAX77620
 	  driver also provides interrupt support for each of the gpios.
 	  Say yes here to enable the max77620 to be used as gpio controller.
 
+config GPIO_MAX77650
+	tristate "Maxim MAX77650/77651 GPIO support"
+	depends on MFD_MAX77650
+	help
+	  GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
+	  These chips have a single pin that can be configured as GPIO.
+
 config GPIO_MSIC
 	bool "Intel MSIC mixed signal gpio support"
 	depends on (X86 || COMPILE_TEST) && MFD_INTEL_MSIC
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 54d55274b93a..075722d8317d 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_GPIO_MAX7300)	+= gpio-max7300.o
 obj-$(CONFIG_GPIO_MAX7301)	+= gpio-max7301.o
 obj-$(CONFIG_GPIO_MAX732X)	+= gpio-max732x.o
 obj-$(CONFIG_GPIO_MAX77620)	+= gpio-max77620.o
+obj-$(CONFIG_GPIO_MAX77650)	+= gpio-max77650.o
 obj-$(CONFIG_GPIO_MB86S7X)	+= gpio-mb86s7x.o
 obj-$(CONFIG_GPIO_MENZ127)	+= gpio-menz127.o
 obj-$(CONFIG_GPIO_MERRIFIELD)	+= gpio-merrifield.o
diff --git a/drivers/gpio/gpio-max77650.c b/drivers/gpio/gpio-max77650.c
new file mode 100644
index 000000000000..3f03f4e8956c
--- /dev/null
+++ b/drivers/gpio/gpio-max77650.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// GPIO driver for MAXIM 77650/77651 charger/power-supply.
+
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77650_GPIO_DIR_MASK		BIT(0)
+#define MAX77650_GPIO_INVAL_MASK	BIT(1)
+#define MAX77650_GPIO_DRV_MASK		BIT(2)
+#define MAX77650_GPIO_OUTVAL_MASK	BIT(3)
+#define MAX77650_GPIO_DEBOUNCE_MASK	BIT(4)
+
+#define MAX77650_GPIO_DIR_OUT		0x00
+#define MAX77650_GPIO_DIR_IN		BIT(0)
+#define MAX77650_GPIO_OUT_LOW		0x00
+#define MAX77650_GPIO_OUT_HIGH		BIT(3)
+#define MAX77650_GPIO_DRV_OPEN_DRAIN	0x00
+#define MAX77650_GPIO_DRV_PUSH_PULL	BIT(2)
+#define MAX77650_GPIO_DEBOUNCE		BIT(4)
+
+#define MAX77650_GPIO_DIR_BITS(_reg) \
+		((_reg) & MAX77650_GPIO_DIR_MASK)
+#define MAX77650_GPIO_INVAL_BITS(_reg) \
+		(((_reg) & MAX77650_GPIO_INVAL_MASK) >> 1)
+
+struct max77650_gpio_chip {
+	struct regmap *map;
+	struct gpio_chip gc;
+	int irq;
+};
+
+static int max77650_gpio_direction_input(struct gpio_chip *gc,
+					 unsigned int offset)
+{
+	struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+
+	return regmap_update_bits(chip->map,
+				  MAX77650_REG_CNFG_GPIO,
+				  MAX77650_GPIO_DIR_MASK,
+				  MAX77650_GPIO_DIR_IN);
+}
+
+static int max77650_gpio_direction_output(struct gpio_chip *gc,
+					  unsigned int offset, int value)
+{
+	struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+	int mask, regval;
+
+	mask = MAX77650_GPIO_DIR_MASK | MAX77650_GPIO_OUTVAL_MASK;
+	regval = value ? MAX77650_GPIO_OUT_HIGH : MAX77650_GPIO_OUT_LOW;
+	regval |= MAX77650_GPIO_DIR_OUT;
+
+	return regmap_update_bits(chip->map,
+				  MAX77650_REG_CNFG_GPIO, mask, regval);
+}
+
+static void max77650_gpio_set_value(struct gpio_chip *gc,
+				    unsigned int offset, int value)
+{
+	struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+	int rv, regval;
+
+	regval = value ? MAX77650_GPIO_OUT_HIGH : MAX77650_GPIO_OUT_LOW;
+
+	rv = regmap_update_bits(chip->map, MAX77650_REG_CNFG_GPIO,
+				MAX77650_GPIO_OUTVAL_MASK, regval);
+	if (rv)
+		dev_err(gc->parent, "cannot set GPIO value: %d\n", rv);
+}
+
+static int max77650_gpio_get_value(struct gpio_chip *gc,
+				   unsigned int offset)
+{
+	struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+	unsigned int val;
+	int rv;
+
+	rv = regmap_read(chip->map, MAX77650_REG_CNFG_GPIO, &val);
+	if (rv)
+		return rv;
+
+	return MAX77650_GPIO_INVAL_BITS(val);
+}
+
+static int max77650_gpio_get_direction(struct gpio_chip *gc,
+				       unsigned int offset)
+{
+	struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+	unsigned int val;
+	int rv;
+
+	rv = regmap_read(chip->map, MAX77650_REG_CNFG_GPIO, &val);
+	if (rv)
+		return rv;
+
+	return MAX77650_GPIO_DIR_BITS(val);
+}
+
+static int max77650_gpio_set_config(struct gpio_chip *gc,
+				    unsigned int offset, unsigned long cfg)
+{
+	struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+
+	switch (pinconf_to_config_param(cfg)) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		return regmap_update_bits(chip->map,
+					  MAX77650_REG_CNFG_GPIO,
+					  MAX77650_GPIO_DRV_MASK,
+					  MAX77650_GPIO_DRV_OPEN_DRAIN);
+	case PIN_CONFIG_DRIVE_PUSH_PULL:
+		return regmap_update_bits(chip->map,
+					  MAX77650_REG_CNFG_GPIO,
+					  MAX77650_GPIO_DRV_MASK,
+					  MAX77650_GPIO_DRV_PUSH_PULL);
+	case PIN_CONFIG_INPUT_DEBOUNCE:
+		return regmap_update_bits(chip->map,
+					  MAX77650_REG_CNFG_GPIO,
+					  MAX77650_GPIO_DEBOUNCE_MASK,
+					  MAX77650_GPIO_DEBOUNCE);
+	default:
+		return -ENOTSUPP;
+	}
+}
+
+static int max77650_gpio_to_irq(struct gpio_chip *gc, unsigned int offset)
+{
+	struct max77650_gpio_chip *chip = gpiochip_get_data(gc);
+
+	return chip->irq;
+}
+
+static int max77650_gpio_probe(struct platform_device *pdev)
+{
+	struct max77650_gpio_chip *chip;
+	struct device *dev, *parent;
+	struct i2c_client *i2c;
+
+	dev = &pdev->dev;
+	parent = dev->parent;
+	i2c = to_i2c_client(parent);
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->map = dev_get_regmap(parent, NULL);
+	if (!chip->map)
+		return -ENODEV;
+
+	chip->irq = platform_get_irq_byname(pdev, "GPI");
+	if (chip->irq < 0)
+		return chip->irq;
+
+	chip->gc.base = -1;
+	chip->gc.ngpio = 1;
+	chip->gc.label = i2c->name;
+	chip->gc.parent = dev;
+	chip->gc.owner = THIS_MODULE;
+	chip->gc.can_sleep = true;
+
+	chip->gc.direction_input = max77650_gpio_direction_input;
+	chip->gc.direction_output = max77650_gpio_direction_output;
+	chip->gc.set = max77650_gpio_set_value;
+	chip->gc.get = max77650_gpio_get_value;
+	chip->gc.get_direction = max77650_gpio_get_direction;
+	chip->gc.set_config = max77650_gpio_set_config;
+	chip->gc.to_irq = max77650_gpio_to_irq;
+
+	return devm_gpiochip_add_data(dev, &chip->gc, chip);
+}
+
+static struct platform_driver max77650_gpio_driver = {
+	.driver = {
+		.name = "max77650-gpio",
+	},
+	.probe = max77650_gpio_probe,
+};
+module_platform_driver(max77650_gpio_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 GPIO driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 07/11] power: supply: max77650: add support for battery charger
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add basic support for the battery charger for max77650 PMIC.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/power/supply/Kconfig            |   7 +
 drivers/power/supply/Makefile           |   1 +
 drivers/power/supply/max77650-charger.c | 367 ++++++++++++++++++++++++
 3 files changed, 375 insertions(+)
 create mode 100644 drivers/power/supply/max77650-charger.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index e901b9879e7e..0230c96fa94d 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -499,6 +499,13 @@ config CHARGER_DETECTOR_MAX14656
 	  Revision 1.2 and can be found e.g. in Kindle 4/5th generation
 	  readers and certain LG devices.
 
+config CHARGER_MAX77650
+	tristate "Maxim MAX77650 battery charger driver"
+	depends on MFD_MAX77650
+	help
+	  Say Y to enable support for the battery charger control of MAX77650
+	  PMICs.
+
 config CHARGER_MAX77693
 	tristate "Maxim MAX77693 battery charger driver"
 	depends on MFD_MAX77693
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index b731c2a9b695..b73eb8c5c1a9 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_CHARGER_MANAGER)	+= charger-manager.o
 obj-$(CONFIG_CHARGER_LTC3651)	+= ltc3651-charger.o
 obj-$(CONFIG_CHARGER_MAX14577)	+= max14577_charger.o
 obj-$(CONFIG_CHARGER_DETECTOR_MAX14656)	+= max14656_charger_detector.o
+obj-$(CONFIG_CHARGER_MAX77650)	+= max77650-charger.o
 obj-$(CONFIG_CHARGER_MAX77693)	+= max77693_charger.o
 obj-$(CONFIG_CHARGER_MAX8997)	+= max8997_charger.o
 obj-$(CONFIG_CHARGER_MAX8998)	+= max8998_charger.o
diff --git a/drivers/power/supply/max77650-charger.c b/drivers/power/supply/max77650-charger.c
new file mode 100644
index 000000000000..e7cca32944bd
--- /dev/null
+++ b/drivers/power/supply/max77650-charger.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// Battery charger driver for MAXIM 77650/77651 charger/power-supply.
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+
+#define MAX77650_CHARGER_ENABLED		BIT(0)
+#define MAX77650_CHARGER_DISABLED		0x00
+#define MAX77650_CHARGER_CHG_EN_MASK		BIT(0)
+
+#define MAX77650_CHG_DETAILS_MASK		GENMASK(7, 4)
+#define MAX77650_CHG_DETAILS_BITS(_reg) \
+		(((_reg) & MAX77650_CHG_DETAILS_MASK) >> 4)
+
+/* Charger is OFF. */
+#define MAX77650_CHG_OFF			0x00
+/* Charger is in prequalification mode. */
+#define MAX77650_CHG_PREQ			0x01
+/* Charger is in fast-charge constant current mode. */
+#define MAX77650_CHG_ON_CURR			0x02
+/* Charger is in JEITA modified fast-charge constant-current mode. */
+#define MAX77650_CHG_ON_CURR_JEITA		0x03
+/* Charger is in fast-charge constant-voltage mode. */
+#define MAX77650_CHG_ON_VOLT			0x04
+/* Charger is in JEITA modified fast-charge constant-voltage mode. */
+#define MAX77650_CHG_ON_VOLT_JEITA		0x05
+/* Charger is in top-off mode. */
+#define MAX77650_CHG_ON_TOPOFF			0x06
+/* Charger is in JEITA modified top-off mode. */
+#define MAX77650_CHG_ON_TOPOFF_JEITA		0x07
+/* Charger is done. */
+#define MAX77650_CHG_DONE			0x08
+/* Charger is JEITA modified done. */
+#define MAX77650_CHG_DONE_JEITA			0x09
+/* Charger is suspended due to a prequalification timer fault. */
+#define MAX77650_CHG_SUSP_PREQ_TIM_FAULT	0x0a
+/* Charger is suspended due to a fast-charge timer fault. */
+#define MAX77650_CHG_SUSP_FAST_CHG_TIM_FAULT	0x0b
+/* Charger is suspended due to a battery temperature fault. */
+#define MAX77650_CHG_SUSP_BATT_TEMP_FAULT	0x0c
+
+#define MAX77650_CHGIN_DETAILS_MASK		GENMASK(3, 2)
+#define MAX77650_CHGIN_DETAILS_BITS(_reg) \
+		(((_reg) & MAX77650_CHGIN_DETAILS_MASK) >> 2)
+
+#define MAX77650_CHGIN_UNDERVOLTAGE_LOCKOUT	0x00
+#define MAX77650_CHGIN_OVERVOLTAGE_LOCKOUT	0x01
+#define MAX77650_CHGIN_OKAY			0x11
+
+#define MAX77650_CHARGER_CHG_MASK	BIT(1)
+#define MAX77650_CHARGER_CHG_CHARGING(_reg) \
+		(((_reg) & MAX77650_CHARGER_CHG_MASK) > 1)
+
+#define MAX77650_CHARGER_VCHGIN_MIN_MASK	0xc0
+#define MAX77650_CHARGER_VCHGIN_MIN_SHIFT(_val)	((_val) << 5)
+
+#define MAX77650_CHARGER_ICHGIN_LIM_MASK	0x1c
+#define MAX77650_CHARGER_ICHGIN_LIM_SHIFT(_val)	((_val) << 2)
+
+struct max77650_charger_data {
+	struct regmap *map;
+	struct device *dev;
+};
+
+static enum power_supply_property max77650_charger_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_CHARGE_TYPE
+};
+
+static const unsigned int max77650_charger_vchgin_min_table[] = {
+	4000000, 4100000, 4200000, 4300000, 4400000, 4500000, 4600000, 4700000
+};
+
+static const unsigned int max77650_charger_ichgin_lim_table[] = {
+	95000, 190000, 285000, 380000, 475000
+};
+
+static int max77650_charger_set_vchgin_min(struct max77650_charger_data *chg,
+					   unsigned int val)
+{
+	int i, rv;
+
+	for (i = 0; i < ARRAY_SIZE(max77650_charger_vchgin_min_table); i++) {
+		if (val == max77650_charger_vchgin_min_table[i]) {
+			rv = regmap_update_bits(chg->map,
+					MAX77650_REG_CNFG_CHG_B,
+					MAX77650_CHARGER_VCHGIN_MIN_MASK,
+					MAX77650_CHARGER_VCHGIN_MIN_SHIFT(i));
+			if (rv)
+				return rv;
+
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int max77650_charger_set_ichgin_lim(struct max77650_charger_data *chg,
+					   unsigned int val)
+{
+	int i, rv;
+
+	for (i = 0; i < ARRAY_SIZE(max77650_charger_ichgin_lim_table); i++) {
+		if (val == max77650_charger_ichgin_lim_table[i]) {
+			rv = regmap_update_bits(chg->map,
+					MAX77650_REG_CNFG_CHG_B,
+					MAX77650_CHARGER_ICHGIN_LIM_MASK,
+					MAX77650_CHARGER_ICHGIN_LIM_SHIFT(i));
+			if (rv)
+				return rv;
+
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int max77650_charger_enable(struct max77650_charger_data *chg)
+{
+	int rv;
+
+	rv = regmap_update_bits(chg->map,
+				MAX77650_REG_CNFG_CHG_B,
+				MAX77650_CHARGER_CHG_EN_MASK,
+				MAX77650_CHARGER_ENABLED);
+	if (rv)
+		dev_err(chg->dev, "unable to enable the charger: %d\n", rv);
+
+	return rv;
+}
+
+static int max77650_charger_disable(struct max77650_charger_data *chg)
+{
+	int rv;
+
+	rv = regmap_update_bits(chg->map,
+				MAX77650_REG_CNFG_CHG_B,
+				MAX77650_CHARGER_CHG_EN_MASK,
+				MAX77650_CHARGER_DISABLED);
+	if (rv)
+		dev_err(chg->dev, "unable to disable the charger: %d\n", rv);
+
+	return rv;
+}
+
+static irqreturn_t max77650_charger_check_status(int irq, void *data)
+{
+	struct max77650_charger_data *chg = data;
+	int rv, reg;
+
+	rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, &reg);
+	if (rv) {
+		dev_err(chg->dev,
+			"unable to read the charger status: %d\n", rv);
+		return IRQ_HANDLED;
+	}
+
+	switch (MAX77650_CHGIN_DETAILS_BITS(reg)) {
+	case MAX77650_CHGIN_UNDERVOLTAGE_LOCKOUT:
+		dev_err(chg->dev, "undervoltage lockout detected, disabling charger\n");
+		max77650_charger_disable(chg);
+		break;
+	case MAX77650_CHGIN_OVERVOLTAGE_LOCKOUT:
+		dev_err(chg->dev, "overvoltage lockout detected, disabling charger\n");
+		max77650_charger_disable(chg);
+		break;
+	case MAX77650_CHGIN_OKAY:
+		max77650_charger_enable(chg);
+		break;
+	default:
+		/* May be 0x10 - debouncing */
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int max77650_charger_get_property(struct power_supply *psy,
+					 enum power_supply_property psp,
+					 union power_supply_propval *val)
+{
+	struct max77650_charger_data *chg = power_supply_get_drvdata(psy);
+	int rv, reg;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, &reg);
+		if (rv)
+			return rv;
+
+		if (MAX77650_CHARGER_CHG_CHARGING(reg)) {
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+			break;
+		}
+
+		switch (MAX77650_CHG_DETAILS_BITS(reg)) {
+		case MAX77650_CHG_OFF:
+		case MAX77650_CHG_SUSP_PREQ_TIM_FAULT:
+		case MAX77650_CHG_SUSP_FAST_CHG_TIM_FAULT:
+		case MAX77650_CHG_SUSP_BATT_TEMP_FAULT:
+			val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+			break;
+		case MAX77650_CHG_PREQ:
+		case MAX77650_CHG_ON_CURR:
+		case MAX77650_CHG_ON_CURR_JEITA:
+		case MAX77650_CHG_ON_VOLT:
+		case MAX77650_CHG_ON_VOLT_JEITA:
+		case MAX77650_CHG_ON_TOPOFF:
+		case MAX77650_CHG_ON_TOPOFF_JEITA:
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+			break;
+		case MAX77650_CHG_DONE:
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+			break;
+		default:
+			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+		}
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, &reg);
+		if (rv)
+			return rv;
+
+		val->intval = MAX77650_CHARGER_CHG_CHARGING(reg);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		rv = regmap_read(chg->map, MAX77650_REG_STAT_CHG_B, &reg);
+		if (rv)
+			return rv;
+
+		if (!MAX77650_CHARGER_CHG_CHARGING(reg)) {
+			val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+			break;
+		}
+
+		switch (MAX77650_CHG_DETAILS_BITS(reg)) {
+		case MAX77650_CHG_PREQ:
+		case MAX77650_CHG_ON_CURR:
+		case MAX77650_CHG_ON_CURR_JEITA:
+		case MAX77650_CHG_ON_VOLT:
+		case MAX77650_CHG_ON_VOLT_JEITA:
+			val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+			break;
+		case MAX77650_CHG_ON_TOPOFF:
+		case MAX77650_CHG_ON_TOPOFF_JEITA:
+			val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+			break;
+		default:
+			val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct power_supply_desc max77650_battery_desc = {
+	.name		= "max77650",
+	.type		= POWER_SUPPLY_TYPE_USB,
+	.get_property	= max77650_charger_get_property,
+	.properties	= max77650_charger_properties,
+	.num_properties	= ARRAY_SIZE(max77650_charger_properties),
+};
+
+static int max77650_charger_probe(struct platform_device *pdev)
+{
+	struct power_supply_config pscfg = {};
+	struct max77650_charger_data *chg;
+	struct power_supply *battery;
+	struct device *dev, *parent;
+	int rv, chg_irq, chgin_irq;
+	unsigned int prop;
+
+	dev = &pdev->dev;
+	parent = dev->parent;
+
+	chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL);
+	if (!chg)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, chg);
+
+	chg->map = dev_get_regmap(parent, NULL);
+	if (!chg->map)
+		return -ENODEV;
+
+	chg->dev = dev;
+
+	pscfg.of_node = dev->of_node;
+	pscfg.drv_data = chg;
+
+	chg_irq = platform_get_irq_byname(pdev, "CHG");
+	if (chg_irq < 0)
+		return chg_irq;
+
+	chgin_irq = platform_get_irq_byname(pdev, "CHGIN");
+	if (chgin_irq < 0)
+		return chgin_irq;
+
+	rv = devm_request_any_context_irq(dev, chg_irq,
+					  max77650_charger_check_status,
+					  IRQF_ONESHOT, "chg", chg);
+	if (rv < 0)
+		return rv;
+
+	rv = devm_request_any_context_irq(dev, chgin_irq,
+					  max77650_charger_check_status,
+					  IRQF_ONESHOT, "chgin", chg);
+	if (rv < 0)
+		return rv;
+
+	battery = devm_power_supply_register(dev,
+					     &max77650_battery_desc, &pscfg);
+	if (IS_ERR(battery))
+		return PTR_ERR(battery);
+
+	rv = of_property_read_u32(dev->of_node, "min-microvolt", &prop);
+	if (rv == 0) {
+		rv = max77650_charger_set_vchgin_min(chg, prop);
+		if (rv)
+			return rv;
+	}
+
+	rv = of_property_read_u32(dev->of_node,
+				  "current-limit-microamp", &prop);
+	if (rv == 0) {
+		rv = max77650_charger_set_ichgin_lim(chg, prop);
+		if (rv)
+			return rv;
+	}
+
+	return max77650_charger_enable(chg);
+}
+
+static int max77650_charger_remove(struct platform_device *pdev)
+{
+	struct max77650_charger_data *chg = platform_get_drvdata(pdev);
+
+	return max77650_charger_disable(chg);
+}
+
+static struct platform_driver max77650_charger_driver = {
+	.driver = {
+		.name = "max77650-charger",
+	},
+	.probe = max77650_charger_probe,
+	.remove = max77650_charger_remove,
+};
+module_platform_driver(max77650_charger_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 charger driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 06/11] mfd: max77650: new core mfd driver
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add the core mfd driver for max77650 PMIC. We define five sub-devices
for which the drivers will be added in subsequent patches.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 drivers/mfd/Kconfig          |  14 +++
 drivers/mfd/Makefile         |   1 +
 drivers/mfd/max77650.c       | 234 +++++++++++++++++++++++++++++++++++
 include/linux/mfd/max77650.h |  59 +++++++++
 4 files changed, 308 insertions(+)
 create mode 100644 drivers/mfd/max77650.c
 create mode 100644 include/linux/mfd/max77650.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 0ce2d8dfc5f1..ade04e124aa0 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -733,6 +733,20 @@ config MFD_MAX77620
 	  provides common support for accessing the device; additional drivers
 	  must be enabled in order to use the functionality of the device.
 
+config MFD_MAX77650
+	tristate "Maxim MAX77650/77651 PMIC Support"
+	depends on I2C
+	depends on OF || COMPILE_TEST
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Say Y here to add support for Maxim Semiconductor MAX77650 and
+	  MAX77651 Power Management ICs. This is the core multifunction
+	  driver for interacting with the device. The module name is
+	  'max77650'. Additional drivers can be enabled in order to use
+	  the following functionalities of the device: GPIO, regulator,
+	  charger, LED, onkey.
+
 config MFD_MAX77686
 	tristate "Maxim Semiconductor MAX77686/802 PMIC Support"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b4569ed7f3f3..5727d099c16f 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -155,6 +155,7 @@ obj-$(CONFIG_MFD_DA9150)	+= da9150-core.o
 
 obj-$(CONFIG_MFD_MAX14577)	+= max14577.o
 obj-$(CONFIG_MFD_MAX77620)	+= max77620.o
+obj-$(CONFIG_MFD_MAX77650)	+= max77650.o
 obj-$(CONFIG_MFD_MAX77686)	+= max77686.o
 obj-$(CONFIG_MFD_MAX77693)	+= max77693.o
 obj-$(CONFIG_MFD_MAX77843)	+= max77843.o
diff --git a/drivers/mfd/max77650.c b/drivers/mfd/max77650.c
new file mode 100644
index 000000000000..7a6c0a5cf602
--- /dev/null
+++ b/drivers/mfd/max77650.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// Core MFD driver for MAXIM 77650/77651 charger/power-supply.
+// Programming manual: https://pdfserv.maximintegrated.com/en/an/AN6428.pdf
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#define MAX77650_INT_GPI_F_MSK		BIT(0)
+#define MAX77650_INT_GPI_R_MSK		BIT(1)
+#define MAX77650_INT_GPI_MSK \
+			(MAX77650_INT_GPI_F_MSK | MAX77650_INT_GPI_R_MSK)
+#define MAX77650_INT_nEN_F_MSK		BIT(2)
+#define MAX77650_INT_nEN_R_MSK		BIT(3)
+#define MAX77650_INT_TJAL1_R_MSK	BIT(4)
+#define MAX77650_INT_TJAL2_R_MSK	BIT(5)
+#define MAX77650_INT_DOD_R_MSK		BIT(6)
+
+#define MAX77650_INT_THM_MSK		BIT(0)
+#define MAX77650_INT_CHG_MSK		BIT(1)
+#define MAX77650_INT_CHGIN_MSK		BIT(2)
+#define MAX77650_INT_TJ_REG_MSK		BIT(3)
+#define MAX77650_INT_CHGIN_CTRL_MSK	BIT(4)
+#define MAX77650_INT_SYS_CTRL_MSK	BIT(5)
+#define MAX77650_INT_SYS_CNFG_MSK	BIT(6)
+
+#define MAX77650_INT_GLBL_OFFSET	0
+#define MAX77650_INT_CHG_OFFSET		1
+
+#define MAX77650_SBIA_LPM_MASK		BIT(5)
+#define MAX77650_SBIA_LPM_DISABLED	0x00
+
+enum {
+	MAX77650_INT_GPI,
+	MAX77650_INT_nEN_F,
+	MAX77650_INT_nEN_R,
+	MAX77650_INT_TJAL1_R,
+	MAX77650_INT_TJAL2_R,
+	MAX77650_INT_DOD_R,
+	MAX77650_INT_THM,
+	MAX77650_INT_CHG,
+	MAX77650_INT_CHGIN,
+	MAX77650_INT_TJ_REG,
+	MAX77650_INT_CHGIN_CTRL,
+	MAX77650_INT_SYS_CTRL,
+	MAX77650_INT_SYS_CNFG,
+};
+
+static const struct resource max77650_charger_resources[] = {
+	DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHG, "CHG"),
+	DEFINE_RES_IRQ_NAMED(MAX77650_INT_CHGIN, "CHGIN"),
+};
+
+static const struct resource max77650_gpio_resources[] = {
+	DEFINE_RES_IRQ_NAMED(MAX77650_INT_GPI, "GPI"),
+};
+
+static const struct resource max77650_onkey_resources[] = {
+	DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_F, "nEN_F"),
+	DEFINE_RES_IRQ_NAMED(MAX77650_INT_nEN_R, "nEN_R"),
+};
+
+static const struct mfd_cell max77650_cells[] = {
+	{
+		.name		= "max77650-regulator",
+		.of_compatible	= "maxim,max77650-regulator",
+	},
+	{
+		.name		= "max77650-charger",
+		.of_compatible	= "maxim,max77650-charger",
+		.resources	= max77650_charger_resources,
+		.num_resources	= ARRAY_SIZE(max77650_charger_resources),
+	},
+	{
+		.name		= "max77650-gpio",
+		.of_compatible	= "maxim,max77650-gpio",
+		.resources	= max77650_gpio_resources,
+		.num_resources	= ARRAY_SIZE(max77650_gpio_resources),
+	},
+	{
+		.name		= "max77650-led",
+		.of_compatible	= "maxim,max77650-led",
+	},
+	{
+		.name		= "max77650-onkey",
+		.of_compatible	= "maxim,max77650-onkey",
+		.resources	= max77650_onkey_resources,
+		.num_resources	= ARRAY_SIZE(max77650_onkey_resources),
+	},
+};
+
+static const struct regmap_irq max77650_irqs[] = {
+	[MAX77650_INT_GPI] = {
+		.reg_offset		= MAX77650_INT_GLBL_OFFSET,
+		.mask			= MAX77650_INT_GPI_MSK,
+		.type = {
+			.type_falling_val	= MAX77650_INT_GPI_F_MSK,
+			.type_rising_val	= MAX77650_INT_GPI_R_MSK,
+			.types_supported	= IRQ_TYPE_EDGE_BOTH,
+		},
+	},
+	REGMAP_IRQ_REG(MAX77650_INT_nEN_F,
+		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_F_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_nEN_R,
+		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_nEN_R_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_TJAL1_R,
+		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL1_R_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_TJAL2_R,
+		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_TJAL2_R_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_DOD_R,
+		       MAX77650_INT_GLBL_OFFSET, MAX77650_INT_DOD_R_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_THM,
+		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_THM_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_CHG,
+		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHG_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_CHGIN,
+		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_TJ_REG,
+		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_TJ_REG_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_CHGIN_CTRL,
+		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_CHGIN_CTRL_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_SYS_CTRL,
+		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CTRL_MSK),
+	REGMAP_IRQ_REG(MAX77650_INT_SYS_CNFG,
+		       MAX77650_INT_CHG_OFFSET, MAX77650_INT_SYS_CNFG_MSK),
+};
+
+static const struct regmap_irq_chip max77650_irq_chip = {
+	.name			= "max77650-irq",
+	.irqs			= max77650_irqs,
+	.num_irqs		= ARRAY_SIZE(max77650_irqs),
+	.num_regs		= 2,
+	.status_base		= MAX77650_REG_INT_GLBL,
+	.mask_base		= MAX77650_REG_INTM_GLBL,
+	.type_in_mask		= true,
+	.type_invert		= true,
+	.init_ack_masked	= true,
+	.clear_on_unmask	= true,
+};
+
+static const struct regmap_config max77650_regmap_config = {
+	.name		= "max77650",
+	.reg_bits	= 8,
+	.val_bits	= 8,
+};
+
+static int max77650_i2c_probe(struct i2c_client *i2c)
+{
+	struct regmap_irq_chip_data *irq_data;
+	struct device *dev = &i2c->dev;
+	struct irq_domain *domain;
+	struct regmap *map;
+	unsigned int val;
+	int rv;
+
+	map = devm_regmap_init_i2c(i2c, &max77650_regmap_config);
+	if (IS_ERR(map)) {
+		dev_err(dev, "unable to initialize i2c regmap\n");
+		return PTR_ERR(map);
+	}
+
+	rv = regmap_read(map, MAX77650_REG_CID, &val);
+	if (rv) {
+		dev_err(dev, "unable to read the CID from device\n");
+		return rv;
+	}
+
+	switch (MAX77650_CID_BITS(val)) {
+	case MAX77650_CID_77650A:
+	case MAX77650_CID_77650C:
+	case MAX77650_CID_77651A:
+	case MAX77650_CID_77651B:
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	/*
+	 * This IC has a low-power mode which reduces the quiescent current
+	 * consumption to ~5.6uA but is only suitable for systems consuming
+	 * less than ~2mA. Since this is not likely the case even on
+	 * linux-based wearables - keep the chip in normal power mode.
+	 */
+	rv = regmap_update_bits(map,
+				MAX77650_REG_CNFG_GLBL,
+				MAX77650_SBIA_LPM_MASK,
+				MAX77650_SBIA_LPM_DISABLED);
+	if (rv) {
+		dev_err(dev, "unable to change the power mode\n");
+		return rv;
+	}
+
+	rv = devm_regmap_add_irq_chip(dev, map, i2c->irq,
+				      IRQF_ONESHOT | IRQF_SHARED, 0,
+				      &max77650_irq_chip, &irq_data);
+	if (rv) {
+		dev_err(dev, "unable to add the regmap irq chip\n");
+		return rv;
+	}
+
+	domain = regmap_irq_get_domain(irq_data);
+
+	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+				    max77650_cells, ARRAY_SIZE(max77650_cells),
+				    NULL, 0, domain);
+}
+
+static const struct of_device_id max77650_of_match[] = {
+	{ .compatible = "maxim,max77650" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max77650_of_match);
+
+static struct i2c_driver max77650_i2c_driver = {
+	.driver = {
+		.name = "max77650",
+		.of_match_table = of_match_ptr(max77650_of_match),
+	},
+	.probe_new = max77650_i2c_probe,
+};
+module_i2c_driver(max77650_i2c_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 multi-function core driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/max77650.h b/include/linux/mfd/max77650.h
new file mode 100644
index 000000000000..c809e211a8cd
--- /dev/null
+++ b/include/linux/mfd/max77650.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 BayLibre SAS
+ * Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ *
+ * Common definitions for MAXIM 77650/77651 charger/power-supply.
+ */
+
+#ifndef MAX77650_H
+#define MAX77650_H
+
+#include <linux/bits.h>
+
+#define MAX77650_REG_INT_GLBL		0x00
+#define MAX77650_REG_INT_CHG		0x01
+#define MAX77650_REG_STAT_CHG_A		0x02
+#define MAX77650_REG_STAT_CHG_B		0x03
+#define MAX77650_REG_ERCFLAG		0x04
+#define MAX77650_REG_STAT_GLBL		0x05
+#define MAX77650_REG_INTM_GLBL		0x06
+#define MAX77650_REG_INTM_CHG		0x07
+#define MAX77650_REG_CNFG_GLBL		0x10
+#define MAX77650_REG_CID		0x11
+#define MAX77650_REG_CNFG_GPIO		0x12
+#define MAX77650_REG_CNFG_CHG_A		0x18
+#define MAX77650_REG_CNFG_CHG_B		0x19
+#define MAX77650_REG_CNFG_CHG_C		0x1a
+#define MAX77650_REG_CNFG_CHG_D		0x1b
+#define MAX77650_REG_CNFG_CHG_E		0x1c
+#define MAX77650_REG_CNFG_CHG_F		0x1d
+#define MAX77650_REG_CNFG_CHG_G		0x1e
+#define MAX77650_REG_CNFG_CHG_H		0x1f
+#define MAX77650_REG_CNFG_CHG_I		0x20
+#define MAX77650_REG_CNFG_SBB_TOP	0x28
+#define MAX77650_REG_CNFG_SBB0_A	0x29
+#define MAX77650_REG_CNFG_SBB0_B	0x2a
+#define MAX77650_REG_CNFG_SBB1_A	0x2b
+#define MAX77650_REG_CNFG_SBB1_B	0x2c
+#define MAX77650_REG_CNFG_SBB2_A	0x2d
+#define MAX77650_REG_CNFG_SBB2_B	0x2e
+#define MAX77650_REG_CNFG_LDO_A		0x38
+#define MAX77650_REG_CNFG_LDO_B		0x39
+#define MAX77650_REG_CNFG_LED0_A	0x40
+#define MAX77650_REG_CNFG_LED1_A	0x41
+#define MAX77650_REG_CNFG_LED2_A	0x42
+#define MAX77650_REG_CNFG_LED0_B	0x43
+#define MAX77650_REG_CNFG_LED1_B	0x44
+#define MAX77650_REG_CNFG_LED2_B	0x45
+#define MAX77650_REG_CNFG_LED_TOP	0x46
+
+#define MAX77650_CID_MASK		GENMASK(3, 0)
+#define MAX77650_CID_BITS(_reg)		(_reg & MAX77650_CID_MASK)
+
+#define MAX77650_CID_77650A		0x03
+#define MAX77650_CID_77650C		0x0a
+#define MAX77650_CID_77651A		0x06
+#define MAX77650_CID_77651B		0x08
+
+#endif /* MAX77650_H */
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 05/11] mfd: core: document mfd_add_devices()
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add a kernel doc for mfd_add_devices().

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Acked-by: Pavel Machek <pavel@ucw.cz>
---
 drivers/mfd/mfd-core.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
index 94e3f32ce935..0898a8db1747 100644
--- a/drivers/mfd/mfd-core.c
+++ b/drivers/mfd/mfd-core.c
@@ -269,6 +269,20 @@ static int mfd_add_device(struct device *parent, int id,
 	return ret;
 }
 
+/**
+ * mfd_add_devices - register a set of child devices
+ *
+ * @parent: Parent device for all sub-nodes.
+ * @id: Platform device id. If >= 0, each sub-device will have its cell_id
+ *      added to this number and use it as the platform device id.
+ * @cells: Array of mfd cells describing sub-devices.
+ * @n_devs: Number of sub-devices to register.
+ * @mem_base: Parent register range resource for sub-devices.
+ * @irq_base: Base of the range of virtual interrupt numbers allocated for
+ *            this MFD device. Unused if @domain is specified.
+ * @domain: Interrupt domain used to create mappings for HW interrupt numbers
+ *          specificed in sub-devices' IRQ resources.
+ */
 int mfd_add_devices(struct device *parent, int id,
 		    const struct mfd_cell *cells, int n_devs,
 		    struct resource *mem_base,
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 04/11] dt-bindings: input: add DT bindings for max77650
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski, Rob Herring
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add the DT binding document for the onkey module of max77650.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 .../bindings/input/max77650-onkey.txt         | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/max77650-onkey.txt

diff --git a/Documentation/devicetree/bindings/input/max77650-onkey.txt b/Documentation/devicetree/bindings/input/max77650-onkey.txt
new file mode 100644
index 000000000000..477dc74f452a
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/max77650-onkey.txt
@@ -0,0 +1,26 @@
+Onkey driver for MAX77650 PMIC from Maxim Integrated.
+
+This module is part of the MAX77650 MFD device. For more details
+see Documentation/devicetree/bindings/mfd/max77650.txt.
+
+The onkey controller is represented as a sub-node of the PMIC node on
+the device tree.
+
+Required properties:
+--------------------
+- compatible:		Must be "maxim,max77650-onkey".
+
+Optional properties:
+- linux,code:		The key-code to be reported when the key is pressed.
+			Defaults to KEY_POWER.
+- maxim,onkey-slide:	The system's button is a slide switch, not the default
+			push button.
+
+Example:
+--------
+
+	onkey {
+		compatible = "maxim,max77650-onkey";
+		linux,code = <KEY_END>;
+		maxim,onkey-slide;
+	};
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 03/11] dt-bindings: leds: add DT bindings for max77650
From: Bartosz Golaszewski @ 2019-03-26 17:32 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski, Rob Herring
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add the DT binding document for the LEDs module of max77650.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 .../bindings/leds/leds-max77650.txt           | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/leds-max77650.txt

diff --git a/Documentation/devicetree/bindings/leds/leds-max77650.txt b/Documentation/devicetree/bindings/leds/leds-max77650.txt
new file mode 100644
index 000000000000..3a67115cc1da
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-max77650.txt
@@ -0,0 +1,57 @@
+LED driver for MAX77650 PMIC from Maxim Integrated.
+
+This module is part of the MAX77650 MFD device. For more details
+see Documentation/devicetree/bindings/mfd/max77650.txt.
+
+The LED controller is represented as a sub-node of the PMIC node on
+the device tree.
+
+This device has three current sinks.
+
+Required properties:
+--------------------
+- compatible:		Must be "maxim,max77650-led"
+- #address-cells:	Must be <1>.
+- #size-cells:		Must be <0>.
+
+Each LED is represented as a sub-node of the LED-controller node. Up to
+three sub-nodes can be defined.
+
+Required properties of the sub-node:
+------------------------------------
+
+- reg:			Must be <0>, <1> or <2>.
+
+Optional properties of the sub-node:
+------------------------------------
+
+- label:		See Documentation/devicetree/bindings/leds/common.txt
+- linux,default-trigger: See Documentation/devicetree/bindings/leds/common.txt
+
+For more details, please refer to the generic GPIO DT binding document
+<devicetree/bindings/gpio/gpio.txt>.
+
+Example:
+--------
+
+	leds {
+		compatible = "maxim,max77650-led";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		led@0 {
+			reg = <0>;
+			label = "blue:usr0";
+		};
+
+		led@1 {
+			reg = <1>;
+			label = "red:usr1";
+			linux,default-trigger = "heartbeat";
+		};
+
+		led@2 {
+			reg = <2>;
+			label = "green:usr2";
+		};
+	};
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 02/11] dt-bindings: power: supply: add DT bindings for max77650
From: Bartosz Golaszewski @ 2019-03-26 17:31 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add the DT binding document for the battery charger module of max77650.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
---
 .../power/supply/max77650-charger.txt         | 27 +++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/supply/max77650-charger.txt

diff --git a/Documentation/devicetree/bindings/power/supply/max77650-charger.txt b/Documentation/devicetree/bindings/power/supply/max77650-charger.txt
new file mode 100644
index 000000000000..fef188144386
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/max77650-charger.txt
@@ -0,0 +1,27 @@
+Battery charger driver for MAX77650 PMIC from Maxim Integrated.
+
+This module is part of the MAX77650 MFD device. For more details
+see Documentation/devicetree/bindings/mfd/max77650.txt.
+
+The charger is represented as a sub-node of the PMIC node on the device tree.
+
+Required properties:
+--------------------
+- compatible:		Must be "maxim,max77650-charger"
+
+Optional properties:
+--------------------
+- min-microvolt:	Minimum CHGIN regulation voltage (in microvolts). Must be
+			one of: 4000000, 4100000, 4200000, 4300000, 4400000,
+			4500000, 4600000, 4700000.
+- current-limit-microamp:	CHGIN input current limit (in microamps). Must
+				be one of: 95000, 190000, 285000, 380000, 475000.
+
+Example:
+--------
+
+	charger {
+		compatible = "maxim,max77650-charger";
+		min-microvolt = <4200000>;
+		curr-lim-microamp = <285000>;
+	};
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 01/11] dt-bindings: mfd: add DT bindings for max77650
From: Bartosz Golaszewski @ 2019-03-26 17:31 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski, Rob Herring
In-Reply-To: <20190326173208.30614-1-brgl@bgdev.pl>

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

Add a DT binding document for max77650 ultra-low power PMIC. This
describes the core mfd device and the GPIO module.

Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Reviewed-by: Rob Herring <robh@kernel.org>
Acked-by: Pavel Machek <pavel@ucw.cz>
---
 .../devicetree/bindings/mfd/max77650.txt      | 46 +++++++++++++++++++
 1 file changed, 46 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/max77650.txt

diff --git a/Documentation/devicetree/bindings/mfd/max77650.txt b/Documentation/devicetree/bindings/mfd/max77650.txt
new file mode 100644
index 000000000000..b529d8d19335
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/max77650.txt
@@ -0,0 +1,46 @@
+MAX77650 ultra low-power PMIC from Maxim Integrated.
+
+Required properties:
+-------------------
+- compatible:		Must be "maxim,max77650"
+- reg:			I2C device address.
+- interrupts:		The interrupt on the parent the controller is
+			connected to.
+- interrupt-controller: Marks the device node as an interrupt controller.
+- #interrupt-cells:	Must be <2>.
+
+- gpio-controller:	Marks the device node as a gpio controller.
+- #gpio-cells:		Must be <2>. The first cell is the pin number and
+			the second cell is used to specify the gpio active
+			state.
+
+Optional properties:
+--------------------
+gpio-line-names:	Single string containing the name of the GPIO line.
+
+The GPIO-controller module is represented as part of the top-level PMIC
+node. The device exposes a single GPIO line.
+
+For device-tree bindings of other sub-modules (regulator, power supply,
+LEDs and onkey) refer to the binding documents under the respective
+sub-system directories.
+
+For more details on GPIO bindings, please refer to the generic GPIO DT
+binding document <devicetree/bindings/gpio/gpio.txt>.
+
+Example:
+--------
+
+	pmic@48 {
+		compatible = "maxim,max77650";
+		reg = <0x48>;
+
+		interrupt-controller;
+		interrupt-parent = <&gpio2>;
+		#interrupt-cells = <2>;
+		interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
+
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-line-names = "max77650-charger";
+	};
-- 
2.20.1

^ permalink raw reply related

* [PATCH v7 00/11] mfd: add support for max77650 PMIC
From: Bartosz Golaszewski @ 2019-03-26 17:31 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Linus Walleij, Dmitry Torokhov,
	Jacek Anaszewski, Pavel Machek, Lee Jones, Sebastian Reichel,
	Liam Girdwood, Greg Kroah-Hartman
  Cc: linux-kernel, linux-gpio, devicetree, linux-input, linux-leds,
	linux-pm, Bartosz Golaszewski

From: Bartosz Golaszewski <bgolaszewski@baylibre.com>

This series adds support for max77650 ultra low-power PMIC. It provides
the core mfd driver and a set of five sub-drivers for the regulator,
power supply, gpio, leds and input subsystems.

Patches 1-4 add the DT binding documents. Patch 5 documents mfd_add_devices().
Patches 6-10 add all drivers. Last patch adds a MAINTAINERS entry for this
device.

The regulator part is already upstream.

v1 -> v2:
=========

General:
- use C++ style comments for the SPDX license identifier and the
  copyright header
- s/MODULE_LICENSE("GPL")/MODULE_LICENSE("GPL v2")/
- lookup the virtual interrupt numbers in the MFD driver, setup
  resources for child devices and use platform_get_irq_byname()
  in sub-drivers
- picked up review tags
- use devm_request_any_context_irq() for interrupt requests

LEDs:
- changed the max77650_leds_ prefix to max77650_led_
- drop the max77650_leds structure as the only field it held was the
  regmap pointer, move said pointer to struct max77650_led
- change the driver name to "max77650-led"
- drop the last return value check and return the result of
  regmap_write() directly
- change the labeling scheme to one consistent with other LED drivers

ONKEY:
- drop the key reporting helper and call the input functions directly
  from interrupt handlers
- rename the rv local variable to error
- drop parent device asignment

Regulator:
- drop the unnecessary init_data lookup from the driver code
- drop unnecessary include

Charger:
- disable the charger on driver remove
- change the power supply type to POWER_SUPPLY_TYPE_USB

GPIO:
- drop interrupt support until we have correct implementation of hierarchical
  irqs in gpiolib

v2 -> v3:
=========

General:
- dropped regulator patches as they're already in Mark Brown's branch

LED:
- fix the compatible string in the DT binding example
- use the max_brightness property
- use a common prefix ("MAX77650_LED") for all defines in the driver

MFD:
- add the MODULE_DEVICE_TABLE()
- add a sentinel to the of_device_id array
- constify the pointers to irq names
- use an enum instead of defines for interrupt indexes

v3 -> v4:
=========

GPIO:
- as discussed with Linus Walleij: the gpio-controller is now part of
  the core mfd module (we don't spawn a sub-node anymore), the binding
  document for GPIO has been dropped, the GPIO properties have been
  defined in the binding document for the mfd core, the interrupt
  functionality has been reintroduced with the irq directly passed from
  the mfd part
- due to the above changes the Reviewed-by tag from Linus was dropped

v4 -> v5:
=========

General:
- add a patch documenting mfd_add_devices()

MFD:
- pass the regmap irq_chip irq domain to mfd over mfd_add_devices so that
  the hw interrupts from resources can be correctly mapped to virtual irqs
- remove the enum listing cell indexes
- extend Kconfig help
- add a link to the programming manual
- use REGMAP_IRQ_REG() for regmap interrupts (except for GPI which has
  is composed of two hw interrupts for rising and falling edge)
- add error messages in probe
- use PLATFORM_DEVID_NONE constant in devm_mfd_add_devices()
- set irq_base to 0 in regmap_add_irq_chip() as other users to, it's only
  relevant if it's > 0

Charger:
- use non-maxim specific property names for minimum input voltage and current
  limit
- code shrink by using the enable/disable charger helpers everywhere
- use more descriptive names for constants

Onkey:
- use EV_SW event type for slide mode

LED:
- remove stray " from Kconfig help

v5 -> v6:
=========

MFD:
- remove stray spaces in the binding document
- rename the example dt node
- remove unnecessary interrupt-parent property from the bindings

LED:
- add a missing dependency on LEDS_CLASS to Kconfig

Onkey:
- use boolean for the slide button property

Charger:
- fix the property names in DT example
- make constants even more readable

v6 -> v7:
=========

Charger:
- rename the current limit property to current-limit-microamp

Bartosz Golaszewski (11):
  dt-bindings: mfd: add DT bindings for max77650
  dt-bindings: power: supply: add DT bindings for max77650
  dt-bindings: leds: add DT bindings for max77650
  dt-bindings: input: add DT bindings for max77650
  mfd: core: document mfd_add_devices()
  mfd: max77650: new core mfd driver
  power: supply: max77650: add support for battery charger
  gpio: max77650: add GPIO support
  leds: max77650: add LEDs support
  input: max77650: add onkey support
  MAINTAINERS: add an entry for max77650 mfd driver

 .../bindings/input/max77650-onkey.txt         |  26 ++
 .../bindings/leds/leds-max77650.txt           |  57 +++
 .../devicetree/bindings/mfd/max77650.txt      |  46 +++
 .../power/supply/max77650-charger.txt         |  27 ++
 MAINTAINERS                                   |  14 +
 drivers/gpio/Kconfig                          |   7 +
 drivers/gpio/Makefile                         |   1 +
 drivers/gpio/gpio-max77650.c                  | 190 +++++++++
 drivers/input/misc/Kconfig                    |   9 +
 drivers/input/misc/Makefile                   |   1 +
 drivers/input/misc/max77650-onkey.c           | 121 ++++++
 drivers/leds/Kconfig                          |   6 +
 drivers/leds/Makefile                         |   1 +
 drivers/leds/leds-max77650.c                  | 147 +++++++
 drivers/mfd/Kconfig                           |  14 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/max77650.c                        | 234 +++++++++++
 drivers/mfd/mfd-core.c                        |  14 +
 drivers/power/supply/Kconfig                  |   7 +
 drivers/power/supply/Makefile                 |   1 +
 drivers/power/supply/max77650-charger.c       | 367 ++++++++++++++++++
 include/linux/mfd/max77650.h                  |  59 +++
 22 files changed, 1350 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/max77650-onkey.txt
 create mode 100644 Documentation/devicetree/bindings/leds/leds-max77650.txt
 create mode 100644 Documentation/devicetree/bindings/mfd/max77650.txt
 create mode 100644 Documentation/devicetree/bindings/power/supply/max77650-charger.txt
 create mode 100644 drivers/gpio/gpio-max77650.c
 create mode 100644 drivers/input/misc/max77650-onkey.c
 create mode 100644 drivers/leds/leds-max77650.c
 create mode 100644 drivers/mfd/max77650.c
 create mode 100644 drivers/power/supply/max77650-charger.c
 create mode 100644 include/linux/mfd/max77650.h

-- 
2.20.1

^ permalink raw reply

* [PATCH] HID: quirks: Fix keyboard + touchpad on Lenovo Miix 630
From: Jeffrey Hugo @ 2019-03-26 16:55 UTC (permalink / raw)
  To: jikos, benjamin.tissoires
  Cc: linux-input, bjorn.andersson, lee.jones, linux-arm-msm,
	linux-kernel, Jeffrey Hugo

Similar to commit edfc3722cfef ("HID: quirks: Fix keyboard + touchpad on
Toshiba Click Mini not working"), the Lenovo Miix 630 has a combo
keyboard/touchpad device with vid:pid of 04F3:0400, which is shared with
Elan touchpads.  The combo on the Miix 630 has an ACPI id of QTEC0001,
which is not claimed by the elan_i2c driver, so key on that similar to
what was done for the Toshiba Click Mini.

Signed-off-by: Jeffrey Hugo <jeffrey.l.hugo@gmail.com>
---
 drivers/hid/hid-quirks.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 1148d8c0816a..77ffba48cc73 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -715,7 +715,6 @@ static const struct hid_device_id hid_ignore_list[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_DEALEXTREAME, USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20) },
-	{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, 0x0400) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ETT, USB_DEVICE_ID_TC5UH) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ETT, USB_DEVICE_ID_TC4UM) },
@@ -996,6 +995,10 @@ bool hid_ignore(struct hid_device *hdev)
 		if (hdev->product == 0x0401 &&
 		    strncmp(hdev->name, "ELAN0800", 8) != 0)
 			return true;
+		/* Same with product id 0x0400 */
+		if (hdev->product == 0x0400 &&
+		    strncmp(hdev->name, "QTEC0001", 8) != 0)
+			return true;
 		break;
 	}
 
-- 
2.17.1

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox