diff mbox

[v2,1/2] usb: misc: xapea00x: add driver for Xaptum ENF Access Card

Message ID 20180430125418.31344-2-david.bild@xaptum.com (mailing list archive)
State New, archived
Headers show

Commit Message

David R. Bild April 30, 2018, 12:54 p.m. UTC
This commit adds a driver for the Xaptum ENF Access Card, a TPM2.0
hardware module for authenticating IoT devices and gateways.

The card consists of a SPI TPM 2.0 chip and a USB-SPI bridge. This
driver configures the bridge, registers the bridge as an SPI
controller, and adds the TPM 2.0 as an SPI device.  The in-kernel TPM
2.0 driver is then automatically loaded to configure the TPM and
expose it to userspace.

Signed-off-by: David R. Bild <david.bild@xaptum.com>
---
 MAINTAINERS                                 |   6 +
 drivers/usb/misc/Kconfig                    |   2 +
 drivers/usb/misc/Makefile                   |   1 +
 drivers/usb/misc/xapea00x/Kconfig           |  16 ++
 drivers/usb/misc/xapea00x/Makefile          |   7 +
 drivers/usb/misc/xapea00x/xapea00x-bridge.c | 400 ++++++++++++++++++++++++++
 drivers/usb/misc/xapea00x/xapea00x-core.c   | 426 ++++++++++++++++++++++++++++
 drivers/usb/misc/xapea00x/xapea00x-spi.c    | 209 ++++++++++++++
 drivers/usb/misc/xapea00x/xapea00x.h        |  75 +++++
 9 files changed, 1142 insertions(+)
 create mode 100644 drivers/usb/misc/xapea00x/Kconfig
 create mode 100644 drivers/usb/misc/xapea00x/Makefile
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x-bridge.c
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x-core.c
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x-spi.c
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x.h

Comments

Greg KH April 30, 2018, 4:05 p.m. UTC | #1
On Mon, Apr 30, 2018 at 07:54:17AM -0500, David R. Bild wrote:
> This commit adds a driver for the Xaptum ENF Access Card, a TPM2.0
> hardware module for authenticating IoT devices and gateways.
> 
> The card consists of a SPI TPM 2.0 chip and a USB-SPI bridge. This
> driver configures the bridge, registers the bridge as an SPI
> controller, and adds the TPM 2.0 as an SPI device.  The in-kernel TPM
> 2.0 driver is then automatically loaded to configure the TPM and
> expose it to userspace.

A few random USB driver-like review comments of minor things to tweak
for your next submission.  Overall it looks good to me, but you should
also get the TPM maintainers/developers to review it as I will require
their review before I can take it in the USB tree.

> --- /dev/null
> +++ b/drivers/usb/misc/xapea00x/Kconfig
> @@ -0,0 +1,16 @@
> +config  USB_XAPEA00X
> +        tristate "Xaptum ENF Access card support (XAP-EA-00x)"
> +        depends on USB_SUPPORT
> +        select SPI
> +        select TCG_TPM
> +        select TCG_TIS_SPI

Do you really want to 'select' these?  Why not just depend on them?


> --- /dev/null
> +++ b/drivers/usb/misc/xapea00x/xapea00x-bridge.c
> @@ -0,0 +1,400 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
> + *  TPM 2.0-based hardware module for authenticating IoT devices and
> + *  gateways.
> + *
> + *  Copyright (c) 2017 Xaptum, Inc.

It's 2018 now :)

> + */
> +
> +#include "xapea00x.h"
> +
> +#define XAPEA00X_BR_CMD_READ		  0x00
> +#define XAPEA00X_BR_CMD_WRITE		  0x01
> +#define XAPEA00X_BR_CMD_WRITEREAD	  0x02
> +
> +#define XAPEA00X_BR_BREQTYP_SET		  0x40
> +
> +#define XAPEA00X_BR_BREQ_SET_GPIO_VALUES  0x21
> +#define XAPEA00X_BR_BREQ_SET_GPIO_CS	  0x25
> +#define XAPEA00X_BR_BREQ_SET_SPI_WORD	  0x31
> +
> +#define XAPEA00X_BR_USB_TIMEOUT		  1000 // msecs
> +
> +#define XAPEA00X_BR_CS_DISABLED		  0x00

Odd placement of spaces after the tabs, why?


> +
> +/*******************************************************************************
> + * Bridge USB transfers
> + */
> +
> +struct xapea00x_br_bulk_command {
> +	__u16  reserved1;
> +	__u8   command;
> +	__u8   reserved2;
> +	__le32 length;
> +} __attribute__((__packed__));
> +
> +/**
> + * xapea00x_br_prep_bulk_command - Prepares the bulk command header with
> + * the supplied values.
> + * @hdr: pointer to header to prepare
> + * @command: the command id for the command
> + * @length: length in bytes of the command data
> + *
> + * Context: !in_interrupt()
> + *
> + * Return: If successful, 0. Otherwise a negative error number.
> + */

You don't need kerneldoc comments for static functions.  Nice, but not a
requirement at all.

> +static int xapea00x_br_bulk_read(struct xapea00x_device *dev, void *data,
> +				 int len)
> +{
> +	unsigned int pipe;
> +	void *buf;
> +	int actual_len, retval;
> +
> +	buf = kzalloc(len, GFP_KERNEL);
> +	if (!buf) {
> +		retval = -ENOMEM;
> +		goto out;
> +	}
> +
> +	pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in->bEndpointAddress);
> +	retval = usb_bulk_msg(dev->udev, pipe, buf, len, &actual_len,
> +			      XAPEA00X_BR_USB_TIMEOUT);
> +
> +	if (retval) {
> +		dev_warn(&dev->interface->dev,
> +			 "%s: usb_bulk_msg() failed with %d\n",
> +			 __func__, retval);
> +		goto free_buf;
> +	}

Is this going to get noisy when the device gets removed from the system?

> +
> +	memcpy(data, buf, actual_len);
> +	retval = 0;
> +
> +free_buf:
> +	kzfree(buf);
> +
> +out:
> +	return retval;
> +}
> +
> +/**
> + * xapea00x_br_ctrl_write - Issues a send control transfer to the bridge
> + * chip.
> + * @dev: pointer to the device to write to
> + * @bRequest: the command
> + * @wValue: the command value
> + * @wIndex: the command index
> + * @data: pointer to the command data
> + * @len: length in bytes of the command data
> + *
> + * The possible bRequest, wValue, and wIndex values and data format
> + * are specified in the hardware datasheet.
> + *
> + * Context: !in_interrupt()
> + *
> + * Return: If successful, 0. Otherwise a negative error code.
> + */
> +static int xapea00x_br_ctrl_write(struct xapea00x_device *dev, u8 bRequest,
> +				  u16 wValue, u16 wIndex, u8 *data, u16 len)
> +{
> +	unsigned int pipe;
> +	void *buf;
> +	int retval;
> +
> +	/* control_msg buffer must be dma-capable (e.g.k kmalloc-ed,
> +	 * not stack).	Copy data into such buffer here, so we can use
> +	 * simpler stack allocation in the callers - we have no
> +	 * performance concerns given the small buffers and low
> +	 * throughputs of this device.
> +	 */

This is well known and a requirement for all USB drivers, no need to
have to document it here :)

> +	/* ---------------------- TPM SPI Device ------------------------ */
> +	probe = kzalloc(sizeof(*probe), GFP_KERNEL);
> +	if (!probe) {
> +		retval = -ENOMEM;
> +		goto free_spi;
> +	}
> +	xapea00x_init_async_probe(probe, dev, xapea00x_tpm_probe);
> +
> +	schedule_work(&probe->work);
> +	dev_info(&interface->dev, "scheduled initialization of TPM\n");

We do not care :)

Drivers should be silent, unless something bad happens.  Please remove
noisy stuff like this, and:

> +	/* ---------------------- Finished ------------------------ */
> +	dev_info(&interface->dev, "device connected\n");

That line.  Not needed at all.

> +	return 0;
> +
> +free_spi:
> +	spi_unregister_master(dev->spi_master);
> +
> +free_dev:
> +	kref_put(&dev->kref, xapea00x_delete);
> +
> +	dev_err(&interface->dev, "device failed with %d\n", retval);
> +	return retval;
> +}
> +
> +static void xapea00x_disconnect(struct usb_interface *interface)
> +{
> +	struct xapea00x_device *dev = usb_get_intfdata(interface);
> +
> +	usb_set_intfdata(interface, NULL);
> +	spi_unregister_master(dev->spi_master);
> +
> +	mutex_lock(&dev->usb_mutex);
> +	dev->interface = NULL;
> +	mutex_unlock(&dev->usb_mutex);
> +
> +	kref_put(&dev->kref, xapea00x_delete);
> +
> +	dev_info(&dev->udev->dev, "device disconnected\n");

No need for that either.

> +}
> +
> +static struct usb_driver xapea00x_driver = {
> +	.name		      = "xapea00x",

KBUILD_MOD_NAME instead?

> +	.probe		      = xapea00x_probe,
> +	.disconnect	      = xapea00x_disconnect,
> +	.suspend	      = NULL,
> +	.resume		      = NULL,
> +	.reset_resume	      = NULL,

Leave NULL fields alone, no need to even list them here.

> +	.id_table	      = xapea00x_devices,
> +	.supports_autosuspend = 0

And drop this, as it's set to 0 by default.

> +};
> +
> +module_usb_driver(xapea00x_driver);
> +
> +MODULE_AUTHOR("David R. Bild <david.bild@xaptum.com>");
> +MODULE_DESCRIPTION("Xaptum XAP-EA-00x ENF Access card");
> +MODULE_LICENSE("GPL");
> +
> +MODULE_ALIAS("xapea00x");

Ick, no, the build system will add the properly MODULE_ALIAS to the
code, do not write it out "by hand" here at all.  Bad things can happen
if you get it wrong.

> +struct xapea00x_device {
> +	struct kref kref;
> +
> +	struct usb_device *udev;
> +	/*
> +	 * The interface pointer will be set NULL when the device
> +	 * disconnects.  Accessing it safe only while holding the
> +	 * usb_mutex.
> +	 */
> +	struct usb_interface *interface;
> +	/*
> +	 * Th usb_mutex must be held while synchronous USB requests are
> +	 * in progress. It is acquired during disconnect to be sure
> +	 * that there is not an outstanding request.
> +	 */
> +	struct mutex usb_mutex;
> +
> +	struct usb_endpoint_descriptor *bulk_in;
> +	struct usb_endpoint_descriptor *bulk_out;
> +
> +	u16 pid;
> +	u16 vid;

Why do you care about these vid/pid values?  You set them and never use
them.  If you really needed them, you can get them from the pointer to
the interface above.


thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David R. Bild May 1, 2018, 1:30 p.m. UTC | #2
On Mon, Apr 30, 2018 at 11:05 AM, Greg Kroah-Hartman
<gregkh@linuxfoundation.org> wrote:
>
> On Mon, Apr 30, 2018 at 07:54:17AM -0500, David R. Bild wrote:
> > This commit adds a driver for the Xaptum ENF Access Card, a TPM2.0
> > hardware module for authenticating IoT devices and gateways.
> >
> Overall it looks good to me, but you should
> also get the TPM maintainers/developers to review it as I will require
> their review before I can take it in the USB tree.

Thanks for the review.

> > --- /dev/null
> > +++ b/drivers/usb/misc/xapea00x/Kconfig
> > @@ -0,0 +1,16 @@
> > +config  USB_XAPEA00X
> > +        tristate "Xaptum ENF Access card support (XAP-EA-00x)"
> > +        depends on USB_SUPPORT
> > +        select SPI
> > +        select TCG_TPM
> > +        select TCG_TIS_SPI
>
> Do you really want to 'select' these?  Why not just depend on them?

Here's my reasoning, but I'm happy to change to "depends" if that
better follows common practice.

The device is a USB device and thus will only work on a machine with a
USB host. That's a property of the machine, so we just "depend" on it.

The SPI and TPM support requirements are internal to the device driver
(the device is SPI master, SPI device, and TPM).  This driver needs
those systems enabled, but doesn't require any explicit SPI or TPM
support on the machine (e.g., a physical SPI master).  Someone might
want to build a kernel with this driver, but would have no reason to
explicitly enable the SPI or TPM subsystems (after all, the machine
may not have any SPI or TPM hardware).  So we "select" those here.

(I can also see using logic to argue for depending on USB_SUPPORT and
TCG_TPM, but selecting SPI and TCG_TIS_SPI...)

In the absence of any other guidance, that was my logic.  I'm happy to
change to "depends".

> > +struct xapea00x_device {
> > +     struct kref kref;
> > +
> > +     struct usb_device *udev;
> > +     /*
> > +      * The interface pointer will be set NULL when the device
> > +      * disconnects.  Accessing it safe only while holding the
> > +      * usb_mutex.
> > +      */
> > +     struct usb_interface *interface;
> > +     /*
> > +      * Th usb_mutex must be held while synchronous USB requests are
> > +      * in progress. It is acquired during disconnect to be sure
> > +      * that there is not an outstanding request.
> > +      */
> > +     struct mutex usb_mutex;
> > +
> > +     struct usb_endpoint_descriptor *bulk_in;
> > +     struct usb_endpoint_descriptor *bulk_out;
> > +
> > +     u16 pid;
> > +     u16 vid;
>
> Why do you care about these vid/pid values?  You set them and never use
> them.  If you really needed them, you can get them from the pointer to
> the interface above.

They are used in the TPM initialization code (`xapea00x-tpm.c`).
There are a couple of models of xap-ea-00x with differing
initialization requirements.

Is there a shorter way to get the pid from the usb_device than
    __le16_to_cpu(udev->descriptor.idProduct);
?

I found it cleaner to pull that out into a variable once, rather than
fill the TPM-specific code with that very USB-specific code.

Thanks,
David
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 79bb02ff812f..c0e34a3ef5aa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14795,6 +14795,12 @@  L:	linux-wireless@vger.kernel.org
 S:	Maintained
 F:	drivers/net/wireless/rndis_wlan.c
 
+USB XAPEA00X DRIVER
+M:	David R. Bild <david.bild@xaptum.com>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	drivers/usb/misc/xapea00x/
+
 USB XHCI DRIVER
 M:	Mathias Nyman <mathias.nyman@intel.com>
 L:	linux-usb@vger.kernel.org
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 68d2f2cd17dd..747d7f03fb84 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -275,3 +275,5 @@  config USB_CHAOSKEY
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called chaoskey.
+
+source "drivers/usb/misc/xapea00x/Kconfig"
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 109f54f5b9aa..f3583501547c 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -30,4 +30,5 @@  obj-$(CONFIG_USB_HSIC_USB4604)		+= usb4604.o
 obj-$(CONFIG_USB_CHAOSKEY)		+= chaoskey.o
 
 obj-$(CONFIG_USB_SISUSBVGA)		+= sisusbvga/
+obj-$(CONFIG_USB_XAPEA00X)		+= xapea00x/
 obj-$(CONFIG_USB_LINK_LAYER_TEST)	+= lvstest.o
diff --git a/drivers/usb/misc/xapea00x/Kconfig b/drivers/usb/misc/xapea00x/Kconfig
new file mode 100644
index 000000000000..56c11f6ee7b3
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/Kconfig
@@ -0,0 +1,16 @@ 
+config  USB_XAPEA00X
+        tristate "Xaptum ENF Access card support (XAP-EA-00x)"
+        depends on USB_SUPPORT
+        select SPI
+        select TCG_TPM
+        select TCG_TIS_SPI
+        help
+          Say Y here if you want to support the Xaptum ENF Access
+          modules (XAP-EA-00x) in the USB or Mini PCI-e form
+          factors. The XAP-EA-00x module exposes a TPM 2.0 as
+          /dev/tpmX to use for authenticating with the Xaptum ENF.
+
+          To compile this driver as a module, choose M here. The
+          module will be called xapea00x.
+
+          If unsure, say M.
diff --git a/drivers/usb/misc/xapea00x/Makefile b/drivers/usb/misc/xapea00x/Makefile
new file mode 100644
index 000000000000..c4bcd7524c31
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/Makefile
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the xapea00x driver.
+#
+obj-$(CONFIG_USB_XAPEA00X) += xapea00x.o
+
+xapea00x-y += xapea00x-core.o xapea00x-bridge.o
diff --git a/drivers/usb/misc/xapea00x/xapea00x-bridge.c b/drivers/usb/misc/xapea00x/xapea00x-bridge.c
new file mode 100644
index 000000000000..6d78e83d96f6
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x-bridge.c
@@ -0,0 +1,400 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#include "xapea00x.h"
+
+#define XAPEA00X_BR_CMD_READ		  0x00
+#define XAPEA00X_BR_CMD_WRITE		  0x01
+#define XAPEA00X_BR_CMD_WRITEREAD	  0x02
+
+#define XAPEA00X_BR_BREQTYP_SET		  0x40
+
+#define XAPEA00X_BR_BREQ_SET_GPIO_VALUES  0x21
+#define XAPEA00X_BR_BREQ_SET_GPIO_CS	  0x25
+#define XAPEA00X_BR_BREQ_SET_SPI_WORD	  0x31
+
+#define XAPEA00X_BR_USB_TIMEOUT		  1000 // msecs
+
+#define XAPEA00X_BR_CS_DISABLED		  0x00
+
+/*******************************************************************************
+ * Bridge USB transfers
+ */
+
+struct xapea00x_br_bulk_command {
+	__u16  reserved1;
+	__u8   command;
+	__u8   reserved2;
+	__le32 length;
+} __attribute__((__packed__));
+
+/**
+ * xapea00x_br_prep_bulk_command - Prepares the bulk command header with
+ * the supplied values.
+ * @hdr: pointer to header to prepare
+ * @command: the command id for the command
+ * @length: length in bytes of the command data
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static void xapea00x_br_prep_bulk_command(struct xapea00x_br_bulk_command *hdr,
+					  u8 command, int length)
+{
+	hdr->reserved1 = 0;
+	hdr->command   = command;
+	hdr->reserved2 = 0;
+	hdr->length    = __cpu_to_le32(length);
+}
+
+/**
+ * xapea00x_br_bulk_write - Issues a bulk write to the bridge chip.
+ * @dev: pointer to the device to write to
+ * @command: the command started by this write (WRITE, READ, WRITE_READ)
+ * @data: pointer to the data to write. Must be DMA capable (e.g.,
+ *	  kmalloc-ed, not stack).
+ * @len: length in bytes of the data to write
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_br_bulk_write(struct xapea00x_device *dev,
+				  struct xapea00x_br_bulk_command *header,
+				  const void *data, int len)
+{
+	u8 *buf;
+	unsigned int pipe;
+	int buf_len, actual_len, retval;
+
+	buf_len = sizeof(struct xapea00x_br_bulk_command) + len;
+	buf = kzalloc(buf_len, GFP_KERNEL);
+	if (!buf) {
+		retval = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(buf, header, sizeof(struct xapea00x_br_bulk_command));
+	memcpy(buf + sizeof(struct xapea00x_br_bulk_command), data, len);
+
+	pipe = usb_sndbulkpipe(dev->udev, dev->bulk_out->bEndpointAddress);
+	retval = usb_bulk_msg(dev->udev, pipe, buf, buf_len, &actual_len,
+			      XAPEA00X_BR_USB_TIMEOUT);
+	if (retval) {
+		dev_warn(&dev->interface->dev,
+			 "%s: usb_bulk_msg() failed with %d\n",
+			 __func__, retval);
+		goto free_buf;
+	}
+
+	retval = 0;
+
+free_buf:
+	kzfree(buf);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_br_bulk_read - Issues a bulk read to the bridge chip.
+ * @dev: pointer to the device to read from
+ * @data: pointer to the data read. Must be DMA capable (e.g.,
+ *	  kmalloc-ed, not stack).
+ * @len: length in bytes of the data to read
+ *
+ * Return: If successful, 0. Otherwise a negative error code.
+ */
+static int xapea00x_br_bulk_read(struct xapea00x_device *dev, void *data,
+				 int len)
+{
+	unsigned int pipe;
+	void *buf;
+	int actual_len, retval;
+
+	buf = kzalloc(len, GFP_KERNEL);
+	if (!buf) {
+		retval = -ENOMEM;
+		goto out;
+	}
+
+	pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in->bEndpointAddress);
+	retval = usb_bulk_msg(dev->udev, pipe, buf, len, &actual_len,
+			      XAPEA00X_BR_USB_TIMEOUT);
+
+	if (retval) {
+		dev_warn(&dev->interface->dev,
+			 "%s: usb_bulk_msg() failed with %d\n",
+			 __func__, retval);
+		goto free_buf;
+	}
+
+	memcpy(data, buf, actual_len);
+	retval = 0;
+
+free_buf:
+	kzfree(buf);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_br_ctrl_write - Issues a send control transfer to the bridge
+ * chip.
+ * @dev: pointer to the device to write to
+ * @bRequest: the command
+ * @wValue: the command value
+ * @wIndex: the command index
+ * @data: pointer to the command data
+ * @len: length in bytes of the command data
+ *
+ * The possible bRequest, wValue, and wIndex values and data format
+ * are specified in the hardware datasheet.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error code.
+ */
+static int xapea00x_br_ctrl_write(struct xapea00x_device *dev, u8 bRequest,
+				  u16 wValue, u16 wIndex, u8 *data, u16 len)
+{
+	unsigned int pipe;
+	void *buf;
+	int retval;
+
+	/* control_msg buffer must be dma-capable (e.g.k kmalloc-ed,
+	 * not stack).	Copy data into such buffer here, so we can use
+	 * simpler stack allocation in the callers - we have no
+	 * performance concerns given the small buffers and low
+	 * throughputs of this device.
+	 */
+	buf = kzalloc(len, GFP_KERNEL);
+	if (!buf) {
+		retval = -ENOMEM;
+		goto out;
+	}
+	memcpy(buf, data, len);
+
+	pipe = usb_sndctrlpipe(dev->udev, 0);
+	retval = usb_control_msg(dev->udev, pipe, bRequest,
+				 XAPEA00X_BR_BREQTYP_SET, wValue, wIndex,
+				 buf, len, XAPEA00X_BR_USB_TIMEOUT);
+	if (retval < 0) {
+		dev_warn(&dev->interface->dev,
+			 "usb_control_msg() failed with %d\n", retval);
+		goto free_buf;
+	}
+
+	retval = 0;
+
+free_buf:
+	kzfree(buf);
+
+out:
+	return retval;
+}
+
+/*******************************************************************************
+ * Bridge configuration commands
+ */
+
+/**
+ * xapea00x_br_set_gpio_value - Sets the value on the specified pin of
+ * the bridge chip.
+ * @dev: pointer to the device containing the bridge whose pin to set
+ * @pin: the number of the pin to set
+ * @value: the value to set the pin to, 0 or 1
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_br_set_gpio_value(struct xapea00x_device *dev, u8 pin,
+				      u8 value)
+{
+	u8 data[4] = { 0, 0, 0, 0 };
+
+	switch (pin) {
+	case 10:
+	case  9:
+	case  8:
+	case  7:
+	case  6:
+		data[0] = value << (pin - 4);
+		data[2] = 1 << (pin - 4);
+		break;
+	case  5:
+		data[0] = value;
+		data[2] = 1;
+		break;
+	case  4:
+	case  3:
+	case  2:
+	case  1:
+	case  0:
+		data[1] = value << (pin + 3);
+		data[3] = 1 << (pin + 3);
+		break;
+	}
+	return xapea00x_br_ctrl_write(dev, XAPEA00X_BR_BREQ_SET_GPIO_VALUES,
+				      0, 0, data, 4);
+}
+
+/**
+ * xapea00x_br_set_gpio_cs - Sets the chip select control on the specified
+ * pin of the bridge chip.
+ * @dev: pointer to the device containing the bridge whose cs to set
+ * @pin: the number of the pin to set
+ * @control: the chip select control value for the pin, 0, 1, or 2
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_br_set_gpio_cs(struct xapea00x_device *dev, u8 pin,
+				   u8 control)
+{
+	u8 data[2] = { pin, control };
+
+	return xapea00x_br_ctrl_write(dev, XAPEA00X_BR_BREQ_SET_GPIO_CS,
+				      0, 0, data, 2);
+}
+
+/*******************************************************************************
+ * Bridge configuration commands
+ */
+/**
+ * xapea00x_br_disable_cs - disable the built-in chip select
+ * capability of the specified channel.	 It does not support holding
+ * the CS active between SPI transfers, a feature required for the
+ * TPM.	 Instead, we manually control the CS pin as a GPIO.
+ * @dev: pointer to the device containing the bridge whose cs to disable
+ * @channel: the SPI channel whose cs to disable
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful 0. Otherwise a negative error number.
+ */
+int xapea00x_br_disable_cs(struct xapea00x_device *dev, u8 channel)
+{
+	return xapea00x_br_set_gpio_cs(dev, channel,
+				       XAPEA00X_BR_CS_DISABLED);
+}
+
+/**
+ * xapea00x_br_assert_cs - assert the chip select pin for the
+ * specified channel.
+ * @dev: pointer to the device containing the bridge who cs to assert
+ * @channel: the SPI channel whose cs to assert
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful 0. Otherwise a negative error number.
+ */
+int xapea00x_br_assert_cs(struct xapea00x_device *dev, u8 channel)
+{
+	return xapea00x_br_set_gpio_value(dev, channel, 0);
+}
+
+/**
+ * xapea00x_br_deassert_cs - deassert the chip select pin for the
+ * specified channel.
+ * @dev: pointer to the device containing the bridge who cs to deassert
+ * @channel: the SPI channel whose cs to deassert
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful 0. Otherwise a negative error number.
+ */
+int xapea00x_br_deassert_cs(struct xapea00x_device *dev, u8 channel)
+{
+	return xapea00x_br_set_gpio_value(dev, channel, 1);
+}
+
+/*******************************************************************************
+ * Bridge SPI reads and writes
+ */
+/**
+ * xeapea00x_spi_read - Performs a read from the active channel
+ * @dev: pointer to the device to perform the read
+ * @rx_buf: pointer to the buffer to read the data into.  Must be
+ *	    DMA-capable (e.g., kmalloc-ed, not stack).
+ * @len: length in bytes of the data to read
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_br_spi_read(struct xapea00x_device *dev, void *rx_buf, int len)
+{
+	struct xapea00x_br_bulk_command header;
+	int retval;
+
+	xapea00x_br_prep_bulk_command(&header, XAPEA00X_BR_CMD_READ, len);
+	retval = xapea00x_br_bulk_write(dev, &header, NULL, 0);
+	if (retval)
+		goto out;
+
+	retval = xapea00x_br_bulk_read(dev, rx_buf, len);
+
+out:
+	return retval;
+}
+
+/**
+ *xapea00x_br_spi_write - Performs a write to the active channel
+ * @dev: pointer to the device to perform the write
+ * @tx_buf: pointer to the data to write. Must be DMA-capable (e.g.,
+ *	    kmalloc-ed, not stack).
+ * @len: length in bytes of the data to write
+ */
+int xapea00x_br_spi_write(struct xapea00x_device *dev, const void *tx_buf,
+			  int len)
+{
+	struct xapea00x_br_bulk_command header;
+	int retval;
+
+	xapea00x_br_prep_bulk_command(&header, XAPEA00X_BR_CMD_WRITE, len);
+	retval = xapea00x_br_bulk_write(dev, &header, tx_buf, len);
+
+	return retval;
+}
+
+/**
+ * xapea00x_br_spi_write_read - Performs a simultaneous write and read on
+ * the active channel
+ * @dev: pointer to the device to perform the write/read
+ * @tx_buf: pointer to the data to write. Must be DMA-capable (e.g.,
+ *	    kmalloc-ed, not stack).
+ * @rx_buf: pointer to the buffer to read the data into. Must be
+ *	    DMA-capable (e.g., kmalloc-ed, not stack).
+ * @len: length in bytes of the data to write/read
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_br_spi_write_read(struct xapea00x_device *dev, const void *tx_buf,
+			       void *rx_buf, int len)
+{
+	struct xapea00x_br_bulk_command header;
+	int retval;
+
+	xapea00x_br_prep_bulk_command(&header, XAPEA00X_BR_CMD_WRITEREAD, len);
+	retval = xapea00x_br_bulk_write(dev, &header, tx_buf, len);
+	if (retval)
+		goto out;
+
+	retval = xapea00x_br_bulk_read(dev, rx_buf, len);
+
+out:
+	return retval;
+}
diff --git a/drivers/usb/misc/xapea00x/xapea00x-core.c b/drivers/usb/misc/xapea00x/xapea00x-core.c
new file mode 100644
index 000000000000..68caf3bf9be6
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x-core.c
@@ -0,0 +1,426 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#include "xapea00x.h"
+
+#define XAPEA00X_TPM_MODALIAS	       "tpm_tis_spi"
+
+#define kref_to_xapea00x(k) container_of(k, struct xapea00x_device, kref)
+
+static void xapea00x_delete(struct kref *kref)
+{
+	struct xapea00x_device *dev = kref_to_xapea00x(kref);
+
+	usb_put_dev(dev->udev);
+	kfree(dev);
+}
+
+/*******************************************************************************
+ * SPI master functions
+ */
+
+/**
+ * xapea00x_spi_setup - Setup the SPI channel for the TPM.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_setup(struct spi_device *spi)
+{
+	struct xapea00x_device *dev;
+	int retval;
+
+	dev = spi_master_get_devdata(spi->master);
+
+	mutex_lock(&dev->usb_mutex);
+	if (!dev->interface) {
+		retval = -ENODEV;
+		goto out;
+	}
+
+	/* Verify that this is the TPM device */
+	if (spi->chip_select != 0) {
+		retval = -EINVAL;
+		goto err;
+	}
+
+	/*
+	 * Disable auto chip select for the TPM channel.
+	 * Must be done after setting the SPI parameters.
+	 */
+	retval = xapea00x_br_disable_cs(dev, 0);
+	if (retval)
+		goto err;
+
+	/* De-assert chip select for the TPM channel. */
+	retval = xapea00x_br_deassert_cs(dev, 0);
+	if (retval)
+		goto err;
+
+	dev_dbg(&dev->interface->dev, "configured spi channel for tpm\n");
+	goto out;
+
+err:
+	dev_err(&dev->interface->dev,
+		"configuring SPI channel failed with %d\n", retval);
+
+out:
+	mutex_unlock(&dev->usb_mutex);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_transfer - Execute a single SPI transfer.
+ * @dev: pointer to the device to do the transfer on
+ * @tx_buf: pointer to the data to send, if not NULL
+ * @rx_buf: pointer to the buffer to store the received data, if not NULL
+ * @len: length in bytes of the data to send/receive
+ * @cs_hold: If non-zero, the chip select will remain asserted
+ * @delay_usecs: If nonzero, how long to delay after last bit transfer
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_spi_transfer(struct xapea00x_device *dev,
+			  const void *tx_buf, void *rx_buf, u32 len,
+			  int cs_hold, u16 delay_usecs)
+{
+	int retval;
+
+	/* Assert chip select */
+	retval = xapea00x_br_assert_cs(dev, 0);
+	if (retval)
+		goto out;
+
+	/* empty transfer */
+	if (!tx_buf && !rx_buf)
+		retval = 0;
+	/* read transfer */
+	else if (!tx_buf)
+		retval = xapea00x_br_spi_read(dev, rx_buf, len);
+	/* write transfer */
+	else if (!rx_buf)
+		retval = xapea00x_br_spi_write(dev, tx_buf, len);
+	/* write_read transfer */
+	else
+		retval = xapea00x_br_spi_write_read(dev, tx_buf, rx_buf, len);
+
+	/* Deassert chip select, if requested */
+	if (!cs_hold)
+		retval = xapea00x_br_deassert_cs(dev, 0);
+
+	/* Delay for the requested time */
+	udelay(delay_usecs);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_spi_transfer_one_message - Execute a full SPI message.
+ * @master: The SPI master on which to execute the message.
+ * @msg: The SPI message to execute.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative erorr number.
+ */
+static int xapea00x_spi_transfer_one_message(struct spi_master *master,
+					     struct spi_message *msg)
+{
+	struct xapea00x_device *dev;
+	struct spi_transfer *xfer;
+	int is_last, retval;
+
+	dev = spi_master_get_devdata(master);
+
+	mutex_lock(&dev->usb_mutex);
+	if (!dev->interface) {
+		retval = -ENODEV;
+		goto out;
+	}
+
+	/* perform all transfers */
+	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+		is_last = list_is_last(&xfer->transfer_list, &msg->transfers);
+
+		/* Transfer message */
+		retval = xapea00x_spi_transfer(dev, xfer->tx_buf,
+					       xfer->rx_buf, xfer->len,
+					       is_last == xfer->cs_change,
+					       xfer->delay_usecs);
+		if (retval)
+			goto out;
+
+		msg->actual_length += xfer->len;
+	}
+
+	retval = 0;
+
+out:
+	msg->status = retval;
+	spi_finalize_current_message(master);
+
+	mutex_unlock(&dev->usb_mutex);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_probe - Register and configure the SPI master.
+ * @dev: the device whose SPI master to register
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_probe(struct xapea00x_device *dev)
+{
+	struct spi_master *spi_master;
+	int retval;
+
+	spi_master = spi_alloc_master(&dev->interface->dev, sizeof(void *));
+	if (!spi_master) {
+		retval = -ENOMEM;
+		goto err_out;
+	}
+
+	spi_master_set_devdata(spi_master, dev);
+
+	spi_master->min_speed_hz = 93 * 1000 + 800; /* 93.9kHz */
+	spi_master->max_speed_hz = 12 * 1000 * 1000; /* 12 MHz */
+
+	spi_master->bus_num = -1; /* dynamically assigned */
+	spi_master->num_chipselect = 1;
+	spi_master->mode_bits = SPI_MODE_0;
+
+	spi_master->flags = 0;
+	spi_master->setup = xapea00x_spi_setup;
+	spi_master->cleanup = NULL;
+	spi_master->transfer_one_message = xapea00x_spi_transfer_one_message;
+
+	retval = spi_register_master(spi_master);
+
+	if (retval)
+		goto free_spi;
+
+	dev->spi_master = spi_master;
+	dev_dbg(&dev->interface->dev, "registered SPI master\n");
+
+	return 0;
+
+free_spi:
+	spi_master_put(spi_master);
+	dev->spi_master = NULL;
+
+err_out:
+	return retval;
+}
+
+struct xapea00x_async_probe {
+	struct work_struct work;
+	struct xapea00x_device *dev;
+};
+
+#define work_to_probe(w) container_of(w, struct xapea00x_async_probe, work)
+
+/**
+ * xapea00x_init_async_probe - initialize an async probe with the
+ * specified values.
+ * @probe: pointer to the async_probe to initialize
+ * @dev: pointer to the device to probe
+ * @f: pointer to the probe function
+ */
+static void xapea00x_init_async_probe(struct xapea00x_async_probe *probe,
+				      struct xapea00x_device *dev,
+				      void (*f)(struct work_struct *work))
+{
+	INIT_WORK(&probe->work, f);
+	probe->dev = dev;
+
+	kref_get(&dev->kref);
+	spi_master_get(dev->spi_master);
+}
+
+/**
+ * xapea00x_cleanup_async_probe - clean up the internals of the async
+ * probe. Call this method after the probe has completed.
+ *
+ * The caller is responsible for freeing the probe itself, if
+ * dynamically allocated.
+ *
+ * @probe: pointer to the async_probe to clean up
+ */
+static void xapea00x_cleanup_async_probe(struct xapea00x_async_probe *probe)
+{
+	spi_master_put(probe->dev->spi_master);
+	kref_put(&probe->dev->kref, xapea00x_delete);
+}
+
+static struct spi_board_info tpm_board_info = {
+	.modalias	 = XAPEA00X_TPM_MODALIAS,
+	.max_speed_hz	 = 43 * 1000 * 1000, // Hz
+	.chip_select	 = 0,
+	.mode		 = SPI_MODE_0,
+	.platform_data	 = NULL,
+	.controller_data = NULL,
+};
+
+/**
+ * xapea00x_tpm_probe - Register and initialize the TPM device
+ * @work: the work struct contained by the xapea00x device
+ *
+ * Context: !in_interrupt()
+ */
+static void xapea00x_tpm_probe(struct work_struct *work)
+{
+	struct xapea00x_async_probe *probe = work_to_probe(work);
+	struct xapea00x_device *dev = probe->dev;
+	struct spi_master *spi_master = dev->spi_master;
+	struct spi_device *tpm;
+	int retval;
+
+	tpm = spi_new_device(spi_master, &tpm_board_info);
+	mutex_lock(&dev->usb_mutex);
+	if (!dev->interface) {
+		retval = -ENODEV;
+		goto out;
+	}
+	if (!tpm) {
+		retval = -ENODEV;
+		dev_err(&dev->interface->dev,
+			"unable to add spi device for TPM\n");
+		goto err;
+	}
+
+	dev->tpm = tpm;
+	dev_info(&dev->interface->dev, "TPM initialization complete\n");
+
+	retval = 0;
+	goto out;
+
+err:
+	dev_err(&dev->interface->dev,
+		"TPM initialization failed with %d\n", retval);
+
+out:
+	mutex_unlock(&dev->usb_mutex);
+	xapea00x_cleanup_async_probe(probe);
+	kzfree(probe);
+}
+
+/*******************************************************************************
+ * USB driver structs and functions
+ */
+
+static const struct usb_device_id xapea00x_devices[] = {
+	{ USB_DEVICE(USB_VENDOR_ID_SILABS, USB_PRODUCT_ID_XAPEA001) },
+	{ USB_DEVICE(USB_VENDOR_ID_XAPTUM, USB_PRODUCT_ID_XAPEA002) },
+	{ USB_DEVICE(USB_VENDOR_ID_XAPTUM, USB_PRODUCT_ID_XAPEA003) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, xapea00x_devices);
+
+static int xapea00x_probe(struct usb_interface *interface,
+			  const struct usb_device_id *id)
+{
+	struct xapea00x_device *dev;
+	struct xapea00x_async_probe *probe;
+	int retval;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	kref_init(&dev->kref);
+	mutex_init(&dev->usb_mutex);
+
+	/* ---------------------- USB ------------------------ */
+	dev->interface = interface;
+	dev->udev = usb_get_dev(interface_to_usbdev(interface));
+
+	dev->vid = __le16_to_cpu(dev->udev->descriptor.idVendor);
+	dev->pid = __le16_to_cpu(dev->udev->descriptor.idProduct);
+
+	retval = usb_find_common_endpoints(interface->cur_altsetting,
+					   &dev->bulk_in, &dev->bulk_out,
+					   NULL, NULL);
+	if (retval) {
+		dev_err(&interface->dev,
+			"could not find both bulk-in and bulk-out endpoints\n");
+		goto free_dev;
+	}
+
+	usb_set_intfdata(interface, dev);
+
+	/* ---------------------- SPI Master ------------------------ */
+	retval = xapea00x_spi_probe(dev);
+	if (retval) {
+		dev_err(&interface->dev, "could not initialize SPI master\n");
+		goto free_dev;
+	}
+
+	/* ---------------------- TPM SPI Device ------------------------ */
+	probe = kzalloc(sizeof(*probe), GFP_KERNEL);
+	if (!probe) {
+		retval = -ENOMEM;
+		goto free_spi;
+	}
+	xapea00x_init_async_probe(probe, dev, xapea00x_tpm_probe);
+
+	schedule_work(&probe->work);
+	dev_info(&interface->dev, "scheduled initialization of TPM\n");
+
+	/* ---------------------- Finished ------------------------ */
+	dev_info(&interface->dev, "device connected\n");
+	return 0;
+
+free_spi:
+	spi_unregister_master(dev->spi_master);
+
+free_dev:
+	kref_put(&dev->kref, xapea00x_delete);
+
+	dev_err(&interface->dev, "device failed with %d\n", retval);
+	return retval;
+}
+
+static void xapea00x_disconnect(struct usb_interface *interface)
+{
+	struct xapea00x_device *dev = usb_get_intfdata(interface);
+
+	usb_set_intfdata(interface, NULL);
+	spi_unregister_master(dev->spi_master);
+
+	mutex_lock(&dev->usb_mutex);
+	dev->interface = NULL;
+	mutex_unlock(&dev->usb_mutex);
+
+	kref_put(&dev->kref, xapea00x_delete);
+
+	dev_info(&dev->udev->dev, "device disconnected\n");
+}
+
+static struct usb_driver xapea00x_driver = {
+	.name		      = "xapea00x",
+	.probe		      = xapea00x_probe,
+	.disconnect	      = xapea00x_disconnect,
+	.suspend	      = NULL,
+	.resume		      = NULL,
+	.reset_resume	      = NULL,
+	.id_table	      = xapea00x_devices,
+	.supports_autosuspend = 0
+};
+
+module_usb_driver(xapea00x_driver);
+
+MODULE_AUTHOR("David R. Bild <david.bild@xaptum.com>");
+MODULE_DESCRIPTION("Xaptum XAP-EA-00x ENF Access card");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("xapea00x");
diff --git a/drivers/usb/misc/xapea00x/xapea00x-spi.c b/drivers/usb/misc/xapea00x/xapea00x-spi.c
new file mode 100644
index 000000000000..8e145b83f73b
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x-spi.c
@@ -0,0 +1,209 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#include "xapea00x.h"
+
+/*******************************************************************************
+ * SPI master functions
+ */
+
+/**
+ * xapea00x_spi_setup - Setup the SPI channel for the TPM.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_setup(struct spi_device *spi)
+{
+	struct xapea00x_device *dev;
+	int retval;
+
+	dev = spi_master_get_devdata(spi->master);
+
+	/* Verify that this is the TPM device */
+	if (spi->chip_select != 0) {
+		retval = -EINVAL;
+		goto err;
+	}
+
+	/* Set the SPI parameters for the TPM channel. */
+	retval = xapea00x_br_set_spi_word(dev, 0, XAPEA00X_TPM_SPI_WORD);
+	if (retval)
+		goto err;
+
+	/*
+	 * Disable auto chip select for the TPM channel.
+	 * Must be done after setting the SPI parameters.
+	 */
+	retval = xapea00x_br_set_gpio_cs(dev, 0, XAPEA00X_GPIO_CS_DISABLED);
+	if (retval)
+		goto err;
+
+	/* De-assert chip select for the TPM channel. */
+	retval = xapea00x_br_set_gpio_value(dev, 0, 1);
+	if (retval)
+		goto err;
+
+	dev_dbg(&dev->interface->dev, "configured spi channel for tpm\n");
+	return 0;
+
+err:
+	dev_err(&dev->interface->dev,
+		"configuring SPI channel failed with %d\n", retval);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_cleanup
+ *
+ * Context: !in_interrupt()
+ */
+static void xapea00x_spi_cleanup(struct spi_device *spi)
+{
+	dev_dbg(&spi->dev, "%s\n", __func__);
+}
+
+/**
+ * xapea00x_spi_transfer - Execute a single SPI transfer.
+ * @dev: pointer to the device to do the transfer on
+ * @tx_buf: pointer to the data to send, if not NULL
+ * @rx_buf: pointer to the buffer to store the received data, if not NULL
+ * @len: length in bytes of the data to send/receive
+ * @cs_hold: If non-zero, the chip select will remain asserted
+ * @delay_usecs: If nonzero, how long to delay after last bit transfer
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_spi_transfer(struct xapea00x_device *dev,
+			  const void *tx_buf, void *rx_buf, u32 len,
+			  int cs_hold, u16 delay_usecs)
+{
+	int retval;
+
+	/* Assert chip select */
+	retval = xapea00x_br_set_gpio_value(dev, 0, 0);
+	if (retval)
+		goto out;
+
+	/* empty transfer */
+	if (!tx_buf && !rx_buf)
+		retval = 0;
+	/* read transfer */
+	else if (!tx_buf)
+		retval = xapea00x_br_spi_read(dev, rx_buf, len);
+	/* write transfer */
+	else if (!rx_buf)
+		retval = xapea00x_br_spi_write(dev, tx_buf, len);
+	/* write_read transfer */
+	else
+		retval = xapea00x_br_spi_write_read(dev, tx_buf, rx_buf, len);
+
+	/* Deassert chip select, if requested */
+	if (!cs_hold)
+		retval = xapea00x_br_set_gpio_value(dev, 0, 1);
+
+	/* Delay for the requested time */
+	udelay(delay_usecs);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_spi_transfer_one_message - Execute a full SPI message.
+ * @master: The SPI master on which to execute the message.
+ * @msg: The SPI message to execute.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative erorr number.
+ */
+static int xapea00x_spi_transfer_one_message(struct spi_master *master,
+					     struct spi_message *msg)
+{
+	struct xapea00x_device *dev;
+	struct spi_transfer *xfer;
+	int is_last, retval;
+
+	dev = spi_master_get_devdata(master);
+
+	/* perform all transfers */
+	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+		is_last = list_is_last(&xfer->transfer_list, &msg->transfers);
+
+		/* Transfer message */
+		retval = xapea00x_spi_transfer(dev, xfer->tx_buf,
+					       xfer->rx_buf, xfer->len,
+					       is_last == xfer->cs_change,
+					       xfer->delay_usecs);
+		if (retval)
+			goto out;
+
+		msg->actual_length += xfer->len;
+	}
+
+	retval = 0;
+
+out:
+	msg->status = retval;
+	spi_finalize_current_message(master);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_probe - Register and configure the SPI master.
+ * @dev: the device whose SPI master to register
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_probe(struct xapea00x_device *dev)
+{
+	struct spi_master *spi_master;
+	int retval;
+
+	spi_master = spi_alloc_master(&dev->udev->dev, sizeof(void *));
+	if (!spi_master) {
+		retval = -ENOMEM;
+		goto err_out;
+	}
+
+	spi_master_set_devdata(spi_master, dev);
+
+	spi_master->min_speed_hz = 93 * 1000 + 800; /* 93.9kHz */
+	spi_master->max_speed_hz = 12 * 1000 * 1000; /* 12 MHz */
+
+	spi_master->bus_num = -1; /* dynamically assigned */
+	spi_master->num_chipselect = XAPEA00X_NUM_CS;
+	spi_master->mode_bits = SPI_MODE_0;
+
+	spi_master->flags = 0;
+	spi_master->setup = xapea00x_spi_setup;
+	spi_master->cleanup = xapea00x_spi_cleanup;
+	spi_master->transfer_one_message = xapea00x_spi_transfer_one_message;
+
+	retval = spi_register_master(spi_master);
+
+	if (retval)
+		goto free_spi;
+
+	dev->spi_master = spi_master;
+	dev_dbg(&dev->interface->dev, "registered SPI master\n");
+
+	return 0;
+
+free_spi:
+	spi_master_put(spi_master);
+	dev->spi_master = NULL;
+
+err_out:
+	return retval;
+}
diff --git a/drivers/usb/misc/xapea00x/xapea00x.h b/drivers/usb/misc/xapea00x/xapea00x.h
new file mode 100644
index 000000000000..07594ec952ef
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x.h
@@ -0,0 +1,75 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#ifndef _XAPEA00X_H
+#define _XAPEA00X_H
+
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#define USB_VENDOR_ID_SILABS           0x10c4
+#define USB_VENDOR_ID_XAPTUM           0x2FE0
+
+#define USB_PRODUCT_ID_XAPEA001        0x8BDE
+#define USB_PRODUCT_ID_XAPEA002        0x8BDE
+#define USB_PRODUCT_ID_XAPEA003        0x8BEE
+
+struct xapea00x_device {
+	struct kref kref;
+
+	struct usb_device *udev;
+	/*
+	 * The interface pointer will be set NULL when the device
+	 * disconnects.  Accessing it safe only while holding the
+	 * usb_mutex.
+	 */
+	struct usb_interface *interface;
+	/*
+	 * Th usb_mutex must be held while synchronous USB requests are
+	 * in progress. It is acquired during disconnect to be sure
+	 * that there is not an outstanding request.
+	 */
+	struct mutex usb_mutex;
+
+	struct usb_endpoint_descriptor *bulk_in;
+	struct usb_endpoint_descriptor *bulk_out;
+
+	u16 pid;
+	u16 vid;
+
+	struct spi_master *spi_master;
+	struct spi_device *tpm;
+};
+
+/* Public bridge functions */
+int xapea00x_br_disable_cs(struct xapea00x_device *dev, u8 channel);
+int xapea00x_br_assert_cs(struct xapea00x_device *dev, u8 channel);
+int xapea00x_br_deassert_cs(struct xapea00x_device *dev, u8 channel);
+
+int xapea00x_br_spi_read(struct xapea00x_device *dev, void *rx_buf, int len);
+int xapea00x_br_spi_write(struct xapea00x_device *dev, const void *tx_buf,
+			  int len);
+int xapea00x_br_spi_write_read(struct xapea00x_device *dev, const void *tx_buf,
+			       void *rx_buf, int len);
+
+/* Shared SPI function */
+int xapea00x_spi_transfer(struct xapea00x_device *dev,
+			  const void *tx_buf, void *rx_buf, u32 len,
+			  int cs_hold, u16 delay_usecs);
+
+/* Shared TPM functions */
+int xapea00x_tpm_platform_initialize(struct xapea00x_device *dev);
+
+#endif /* _XAPEA00X_H */