lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <771a17d1-3f6e-4d77-b74a-15b2d52ccf87@lunn.ch>
Date: Mon, 12 May 2025 04:47:15 +0200
From: Andrew Lunn <andrew@...n.ch>
To: Damien Riégel <damien.riegel@...abs.com>
Cc: Andrew Lunn <andrew+netdev@...n.ch>,
	"David S . Miller" <davem@...emloft.net>,
	Eric Dumazet <edumazet@...gle.com>,
	Jakub Kicinski <kuba@...nel.org>, Paolo Abeni <pabeni@...hat.com>,
	Rob Herring <robh@...nel.org>,
	Krzysztof Kozlowski <krzk+dt@...nel.org>,
	Conor Dooley <conor+dt@...nel.org>,
	Silicon Labs Kernel Team <linux-devel@...abs.com>,
	netdev@...r.kernel.org, devicetree@...r.kernel.org,
	linux-kernel@...r.kernel.org
Subject: Re: [RFC net-next 14/15] net: cpc: add SPI interface driver

On Sun, May 11, 2025 at 09:27:47PM -0400, Damien Riégel wrote:
> This adds support for CPC over SPI. CPC uses a full-duplex protocol.
> Each frame transmission/reception is split into two parts:
>   - read and/or write header + header checksum
>   - read and/or write payload + payload checksum
> 
> Header frames are always 10 bytes (8 bytes of header and 2 bytes of
> checksum). The header contains the size of the payload to receive (size
> to transmit is already known). As the SPI device also has some
> processing to do when it receives a header, the SPI driver must wait for
> the interrupt line to be asserted before clocking the payload.
> 
> The SPI device always expects the chip select to be asserted and
> deasserted after a header, even if there are no payloads to transmit.
> This is used to keep headers transmission synchronized between host and
> device. As some controllers don't support doing that if there is nothing
> to transmit, a null byte is transmitted in that case and it will be
> ignored by the device.
> 
> If there are payloads, the driver will clock the maximum length of the
> two payloads.
> 
> Signed-off-by: Damien Riégel <damien.riegel@...abs.com>
> ---
>  drivers/net/cpc/Kconfig  |   3 +-
>  drivers/net/cpc/Makefile |   2 +-
>  drivers/net/cpc/main.c   |   8 +
>  drivers/net/cpc/spi.c    | 550 +++++++++++++++++++++++++++++++++++++++
>  drivers/net/cpc/spi.h    |  12 +
>  5 files changed, 573 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/net/cpc/spi.c
>  create mode 100644 drivers/net/cpc/spi.h
> 
> diff --git a/drivers/net/cpc/Kconfig b/drivers/net/cpc/Kconfig
> index f31b6837b49..f5159390a82 100644
> --- a/drivers/net/cpc/Kconfig
> +++ b/drivers/net/cpc/Kconfig
> @@ -2,7 +2,8 @@
>  
>  menuconfig CPC
>  	tristate "Silicon Labs Co-Processor Communication (CPC) Protocol"
> -	depends on NET
> +	depends on NET && SPI
> +	select CRC_ITU_T
>  	help
>  	  Provide support for the CPC protocol to Silicon Labs EFR32 devices.
>  
> diff --git a/drivers/net/cpc/Makefile b/drivers/net/cpc/Makefile
> index a61af84df90..195cdf4ad62 100644
> --- a/drivers/net/cpc/Makefile
> +++ b/drivers/net/cpc/Makefile
> @@ -1,5 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0
>  
> -cpc-y := endpoint.o header.o interface.o main.o protocol.o system.o
> +cpc-y := endpoint.o header.o interface.o main.o protocol.o spi.o system.o
>  
>  obj-$(CONFIG_CPC)	+= cpc.o
> diff --git a/drivers/net/cpc/main.c b/drivers/net/cpc/main.c
> index fc46a25f5dc..b4e73145ac2 100644
> --- a/drivers/net/cpc/main.c
> +++ b/drivers/net/cpc/main.c
> @@ -8,6 +8,7 @@
>  
>  #include "cpc.h"
>  #include "header.h"
> +#include "spi.h"
>  #include "system.h"
>  
>  /**
> @@ -126,12 +127,19 @@ static int __init cpc_init(void)
>  	if (err)
>  		bus_unregister(&cpc_bus);
>  
> +	err = cpc_spi_register_driver();
> +	if (err) {
> +		cpc_system_drv_unregister();
> +		bus_unregister(&cpc_bus);
> +	}
> +
>  	return err;
>  }
>  module_init(cpc_init);
>  
>  static void __exit cpc_exit(void)
>  {
> +	cpc_spi_unregister_driver();
>  	cpc_system_drv_unregister();
>  	bus_unregister(&cpc_bus);
>  }
> diff --git a/drivers/net/cpc/spi.c b/drivers/net/cpc/spi.c
> new file mode 100644
> index 00000000000..2b068eeb5d4
> --- /dev/null
> +++ b/drivers/net/cpc/spi.c
> @@ -0,0 +1,550 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2025, Silicon Laboratories, Inc.
> + */
> +
> +#include <linux/atomic.h>
> +#include <linux/crc-itu-t.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/kthread.h>
> +#include <linux/minmax.h>
> +#include <linux/of.h>
> +#include <linux/skbuff.h>
> +#include <linux/slab.h>
> +#include <linux/spi/spi.h>
> +#include <linux/unaligned.h>
> +#include <linux/wait.h>
> +
> +#include "cpc.h"
> +#include "header.h"
> +#include "interface.h"
> +#include "spi.h"
> +
> +#define CPC_SPI_CSUM_SIZE		2
> +#define CPC_SPI_INTERRUPT_MAX_WAIT_MS	1000
> +#define CPC_SPI_MAX_PAYLOAD_SIZE	4096
> +
> +struct cpc_spi {
> +	struct spi_device *spi;
> +	struct cpc_interface *intf;
> +
> +	struct task_struct *task;
> +	wait_queue_head_t event_queue;
> +
> +	struct sk_buff *tx_skb;
> +	u8 tx_csum[CPC_SPI_CSUM_SIZE];
> +
> +	atomic_t event_cond;
> +	struct sk_buff *rx_skb;
> +	unsigned int rx_len;
> +	u8 rx_header[CPC_HEADER_SIZE + CPC_SPI_CSUM_SIZE];
> +};
> +
> +static bool buffer_is_zeroes(const u8 *buffer, size_t length)
> +{
> +	for (size_t i = 0; i < length; i++) {
> +		if (buffer[i] != 0)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +static u16 cpc_spi_csum(const u8 *buffer, size_t length)
> +{
> +	return crc_itu_t(0, buffer, length);
> +}
> +
> +static int cpc_spi_do_xfer_header(struct cpc_spi *ctx)
> +{
> +	struct spi_transfer xfer_header = {
> +		.rx_buf = ctx->rx_header,
> +		.len = CPC_HEADER_SIZE,
> +		.speed_hz = ctx->spi->max_speed_hz,
> +	};
> +	struct spi_transfer xfer_csum = {
> +		.rx_buf = &ctx->rx_header[CPC_HEADER_SIZE],
> +		.len = sizeof(ctx->tx_csum),
> +		.speed_hz = ctx->spi->max_speed_hz,
> +	};
> +	enum cpc_frame_type type;
> +	struct spi_message msg;
> +	size_t payload_len = 0;
> +	struct sk_buff *skb;
> +	u16 rx_csum;
> +	u16 csum;
> +	int ret;
> +
> +	if (ctx->tx_skb) {
> +		u16 tx_hdr_csum = cpc_spi_csum(ctx->tx_skb->data, CPC_HEADER_SIZE);
> +
> +		put_unaligned_le16(tx_hdr_csum, ctx->tx_csum);
> +
> +		xfer_header.tx_buf = ctx->tx_skb->data;
> +		xfer_csum.tx_buf = ctx->tx_csum;
> +	}
> +
> +	spi_message_init(&msg);
> +	spi_message_add_tail(&xfer_header, &msg);
> +	spi_message_add_tail(&xfer_csum, &msg);
> +
> +	ret = spi_sync(ctx->spi, &msg);
> +	if (ret)
> +		return ret;
> +
> +	if (ctx->tx_skb) {
> +		if (skb_headlen(ctx->tx_skb) == CPC_HEADER_SIZE) {
> +			kfree_skb(ctx->tx_skb);
> +			ctx->tx_skb = NULL;
> +		} else {
> +			skb_pull(ctx->tx_skb, CPC_HEADER_SIZE);
> +		}
> +	}
> +
> +	if (buffer_is_zeroes(ctx->rx_header, CPC_HEADER_SIZE))
> +		return 0;
> +
> +	rx_csum = get_unaligned_le16(&ctx->rx_header[CPC_HEADER_SIZE]);
> +	csum = cpc_spi_csum(ctx->rx_header, CPC_HEADER_SIZE);
> +
> +	if (rx_csum != csum || !cpc_header_get_type(ctx->rx_header, &type)) {
> +		/*
> +		 * If the header checksum is invalid, its length can't be trusted, receive
> +		 * the maximum payload length to recover from that situation. If the frame
> +		 * type cannot be extracted from the header, use same recovery mechanism.
> +		 */
> +		ctx->rx_len = CPC_SPI_MAX_PAYLOAD_SIZE;
> +
> +		return 0;
> +	}
> +
> +	if (type == CPC_FRAME_TYPE_DATA)
> +		payload_len = cpc_header_get_payload_len(ctx->rx_header) +
> +			      sizeof(ctx->tx_csum);
> +
> +	skb = cpc_skb_alloc(payload_len, GFP_KERNEL);
> +	if (!skb) {
> +		/*
> +		 * Failed to allocate memory to receive the payload. Driver must clock in
> +		 * these bytes even if there is no room, to keep the sender in sync.
> +		 */
> +		ctx->rx_len = payload_len;
> +
> +		return 0;
> +	}
> +
> +	memcpy(skb_push(skb, CPC_HEADER_SIZE), ctx->rx_header, CPC_HEADER_SIZE);
> +
> +	if (payload_len) {
> +		ctx->rx_skb = skb;
> +		ctx->rx_len = payload_len;
> +	} else {
> +		cpc_interface_receive_frame(ctx->intf, skb);
> +	}
> +
> +	return 0;
> +}
> +
> +static int cpc_spi_do_xfer_notch(struct cpc_spi *ctx)
> +{
> +	struct spi_transfer xfer = {
> +		.tx_buf = ctx->tx_csum,
> +		.len = 1,
> +		.speed_hz = ctx->spi->max_speed_hz,
> +	};
> +	struct spi_message msg;
> +
> +	ctx->tx_csum[0] = 0;
> +
> +	spi_message_init(&msg);
> +	spi_message_add_tail(&xfer, &msg);
> +
> +	return spi_sync(ctx->spi, &msg);
> +}
> +
> +static int cpc_spi_do_xfer_payload(struct cpc_spi *ctx)
> +{
> +	struct spi_transfer shared_xfer = {
> +		.speed_hz = ctx->spi->max_speed_hz,
> +		.rx_buf = NULL,
> +		.tx_buf = NULL,
> +	};
> +	struct spi_transfer pad_xfer1 = {
> +		.speed_hz = ctx->spi->max_speed_hz,
> +		.rx_buf = NULL,
> +		.tx_buf = NULL,
> +	};
> +	struct spi_transfer pad_xfer2 = {
> +		.speed_hz = ctx->spi->max_speed_hz,
> +		.rx_buf = NULL,
> +		.tx_buf = NULL,
> +	};
> +	unsigned int rx_len = ctx->rx_len;
> +	unsigned int tx_data_len;
> +	struct spi_message msg;
> +	int ret;
> +
> +	spi_message_init(&msg);
> +	spi_message_add_tail(&shared_xfer, &msg);
> +
> +	/*
> +	 * This can happen if header checksum was invalid. In that case, protocol
> +	 * mandates to be ready to receive the maximum number of bytes that the
> +	 * device is capable to send, in order to be sure its TX queue is flushed.
> +	 */
> +	if (!ctx->rx_skb && rx_len) {
> +		shared_xfer.rx_buf = kmalloc(rx_len, GFP_KERNEL);
> +		if (!shared_xfer.rx_buf)
> +			return -ENOMEM;
> +
> +		shared_xfer.len = rx_len;
> +	}
> +
> +	if (ctx->rx_skb && !ctx->tx_skb) {
> +		shared_xfer.rx_buf = skb_put(ctx->rx_skb, rx_len);
> +		shared_xfer.len = rx_len;
> +	}
> +
> +	if (ctx->tx_skb) {
> +		u16 csum = ctx->tx_skb->csum;
> +
> +		put_unaligned_le16(csum, ctx->tx_csum);
> +
> +		tx_data_len = ctx->tx_skb->len;
> +
> +		shared_xfer.tx_buf = ctx->tx_skb->data;
> +		shared_xfer.len = tx_data_len;
> +
> +		if (!ctx->rx_skb) {
> +			pad_xfer1.tx_buf = ctx->tx_csum;
> +			pad_xfer1.len = sizeof(ctx->tx_csum);
> +
> +			spi_message_add_tail(&pad_xfer1, &msg);
> +		}
> +	}
> +
> +	if (ctx->rx_skb && ctx->tx_skb) {
> +		unsigned int shared_len;
> +		unsigned int pad_len;
> +
> +		shared_len = min(rx_len, tx_data_len);
> +		pad_len = max(rx_len, tx_data_len) - shared_len;
> +
> +		shared_xfer.rx_buf = skb_put(ctx->rx_skb, shared_len);
> +		shared_xfer.len = shared_len;
> +
> +		if (rx_len < tx_data_len) {
> +			/*
> +			 * |------- RX BUFFER + RX CSUM ------|
> +			 * |------------------- TX BUFFER ------------|---- TX CSUM ----|
> +			 *
> +			 * |             SHARED               |
> +			 *                                    | PAD 1 |
> +			 *                                            |      PAD 2      |
> +			 */
> +			pad_xfer1.rx_buf = NULL;
> +			pad_xfer1.tx_buf = ctx->tx_skb->data + shared_len;
> +			pad_xfer1.len = pad_len;
> +
> +			pad_xfer2.rx_buf = NULL;
> +			pad_xfer2.tx_buf = ctx->tx_csum;
> +			pad_xfer2.len = sizeof(ctx->tx_csum);
> +
> +			spi_message_add_tail(&pad_xfer1, &msg);
> +			spi_message_add_tail(&pad_xfer2, &msg);
> +		} else if (rx_len == tx_data_len) {
> +			/*
> +			 * |------------- RX BUFFER + RX CSUM ---------|
> +			 * |------------------- TX BUFFER -------------|---- TX CSUM ---|
> +			 *
> +			 * |             SHARED                        |
> +			 *                                             |      PAD 1     |
> +			 */
> +			pad_xfer1.rx_buf = NULL;
> +			pad_xfer1.tx_buf = ctx->tx_csum;
> +			pad_xfer1.len = sizeof(ctx->tx_csum);
> +
> +			spi_message_add_tail(&pad_xfer1, &msg);
> +		} else if (rx_len == tx_data_len + 1) {
> +			/*
> +			 * |----------------- RX BUFFER + RX CSUM ----------------|
> +			 * |------------------- TX BUFFER -------------|---- TX CSUM ---|
> +			 *
> +			 * |             SHARED                        |
> +			 *                                             |  PAD 1 |
> +			 *                                                      | PAD 2 |
> +			 */
> +			pad_xfer1.tx_buf = ctx->tx_csum;
> +			pad_xfer1.rx_buf = skb_put(ctx->rx_skb, 1);
> +			pad_xfer1.len = 1;
> +
> +			pad_xfer2.tx_buf = &ctx->tx_csum[1];
> +			pad_xfer2.rx_buf = NULL;
> +			pad_xfer2.len = 1;
> +
> +			spi_message_add_tail(&pad_xfer1, &msg);
> +			spi_message_add_tail(&pad_xfer2, &msg);
> +		} else {
> +			/*
> +			 * |----------------------------- RX BUFFER + RX CSUM -------------------|
> +			 * |------------------- TX BUFFER -------------|---- TX CSUM ---|
> +			 *
> +			 * |             SHARED                        |
> +			 *                                             |       PAD 1    |
> +			 *                                                              |  PAD 2 |
> +			 */
> +			pad_xfer1.tx_buf = ctx->tx_csum;
> +			pad_xfer1.rx_buf = skb_put(ctx->rx_skb, sizeof(ctx->tx_csum));
> +			pad_xfer1.len = sizeof(ctx->tx_csum);
> +
> +			pad_xfer2.tx_buf = NULL;
> +			pad_xfer2.rx_buf = skb_put(ctx->rx_skb, pad_len - sizeof(ctx->tx_csum));
> +			pad_xfer2.len = pad_len - sizeof(ctx->tx_csum);
> +
> +			spi_message_add_tail(&pad_xfer1, &msg);
> +			spi_message_add_tail(&pad_xfer2, &msg);
> +		}
> +	}
> +
> +	ret = spi_sync(ctx->spi, &msg);
> +
> +	if (ctx->tx_skb) {
> +		kfree_skb(ctx->tx_skb);
> +		ctx->tx_skb = NULL;
> +	}
> +
> +	if (ctx->rx_skb) {
> +		unsigned char *csum_ptr;
> +		u16 expected_csum;
> +		u16 csum;
> +
> +		if (ret) {
> +			kfree_skb(ctx->rx_skb);
> +			goto exit;
> +		}
> +
> +		csum_ptr = skb_tail_pointer(ctx->rx_skb) - sizeof(csum);
> +		csum = get_unaligned_le16(csum_ptr);
> +
> +		expected_csum = cpc_spi_csum(ctx->rx_skb->data + CPC_HEADER_SIZE,
> +					     ctx->rx_len - sizeof(csum));
> +
> +		if (csum == expected_csum) {
> +			skb_trim(ctx->rx_skb, ctx->rx_skb->len - sizeof(csum));
> +
> +			cpc_interface_receive_frame(ctx->intf, ctx->rx_skb);
> +		} else {
> +			kfree_skb(ctx->rx_skb);
> +		}
> +	}
> +
> +exit:
> +	ctx->rx_skb = NULL;
> +	ctx->rx_len = 0;
> +
> +	return ret;
> +}
> +
> +static int cpc_spi_do_xfer_thread(void *data)
> +{
> +	struct cpc_spi *ctx = data;
> +	bool xfer_idle = true;
> +	int ret;
> +
> +	while (!kthread_should_stop()) {
> +		if (xfer_idle) {
> +			ret = wait_event_interruptible(ctx->event_queue,
> +						       (!cpc_interface_tx_queue_empty(ctx->intf) ||
> +							atomic_read(&ctx->event_cond) == 1 ||
> +							kthread_should_stop()));
> +
> +			if (ret)
> +				continue;
> +
> +			if (kthread_should_stop())
> +				return 0;
> +
> +			if (!ctx->tx_skb)
> +				ctx->tx_skb = cpc_interface_dequeue(ctx->intf);
> +
> +			/*
> +			 * Reset thread event right before transmission to prevent interrupts that
> +			 * happened while the thread was already awake to wake up the thread again,
> +			 * as the event is going to be handled by this iteration.
> +			 */
> +			atomic_set(&ctx->event_cond, 0);
> +
> +			ret = cpc_spi_do_xfer_header(ctx);
> +			if (!ret)
> +				xfer_idle = false;
> +		} else {
> +			ret = wait_event_timeout(ctx->event_queue,
> +						 (atomic_read(&ctx->event_cond) == 1 ||
> +						  kthread_should_stop()),
> +						 msecs_to_jiffies(CPC_SPI_INTERRUPT_MAX_WAIT_MS));
> +			if (ret == 0) {
> +				dev_err_once(&ctx->spi->dev, "device didn't assert interrupt in a timely manner\n");
> +				continue;
> +			}
> +
> +			atomic_set(&ctx->event_cond, 0);
> +
> +			if (!ctx->tx_skb && !ctx->rx_skb)
> +				ret = cpc_spi_do_xfer_notch(ctx);
> +			else
> +				ret = cpc_spi_do_xfer_payload(ctx);
> +
> +			if (!ret)
> +				xfer_idle = true;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static irqreturn_t cpc_spi_irq_handler(int irq, void *data)
> +{
> +	struct cpc_spi *ctx = data;
> +
> +	atomic_set(&ctx->event_cond, 1);
> +	wake_up(&ctx->event_queue);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int cpc_spi_ops_wake_tx(struct cpc_interface *intf)
> +{
> +	struct cpc_spi *ctx = cpc_interface_get_priv(intf);
> +
> +	wake_up_interruptible(&ctx->event_queue);
> +
> +	return 0;
> +}
> +
> +static void cpc_spi_ops_csum(struct sk_buff *skb)
> +{
> +	skb->csum = cpc_spi_csum(skb->data, skb->len);
> +}
> +
> +static const struct cpc_interface_ops spi_intf_cpc_ops = {
> +	.wake_tx = cpc_spi_ops_wake_tx,
> +	.csum = cpc_spi_ops_csum,
> +};
> +
> +static int cpc_spi_probe(struct spi_device *spi)
> +{
> +	struct cpc_interface *intf;
> +	struct cpc_spi *ctx;
> +	int err;
> +
> +	if (!spi->irq) {
> +		dev_err(&spi->dev, "cannot function without IRQ, please provide one\n");
> +		return -EINVAL;
> +	}
> +
> +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +	if (!ctx)
> +		return -ENOMEM;
> +
> +	intf = cpc_interface_alloc(&spi->dev, &spi_intf_cpc_ops, ctx);
> +	if (IS_ERR(intf)) {
> +		kfree(ctx);
> +
> +		return PTR_ERR(intf);
> +	}
> +
> +	spi_set_drvdata(spi, ctx);
> +
> +	ctx->spi = spi;
> +	ctx->intf = intf;
> +
> +	ctx->tx_skb = NULL;
> +
> +	atomic_set(&ctx->event_cond, 0);
> +	ctx->rx_skb = NULL;
> +
> +	init_waitqueue_head(&ctx->event_queue);
> +
> +	err = cpc_interface_register(intf);
> +	if (err)
> +		goto put_interface;
> +
> +	err = request_irq(spi->irq, cpc_spi_irq_handler, IRQF_TRIGGER_FALLING,
> +			  dev_name(&spi->dev), ctx);
> +	if (err)
> +		goto unregister_interface;
> +
> +	ctx->task = kthread_run(cpc_spi_do_xfer_thread, ctx, "%s",
> +				dev_name(&spi->dev));
> +	if (IS_ERR(ctx->task)) {
> +		err = PTR_ERR(ctx->task);
> +		goto free_irq;
> +	}
> +
> +	return 0;
> +
> +free_irq:
> +	free_irq(spi->irq, ctx);
> +
> +unregister_interface:
> +	cpc_interface_unregister(intf);
> +
> +put_interface:
> +	cpc_interface_put(intf);
> +
> +	kfree(ctx);
> +
> +	return err;
> +}
> +
> +static void cpc_spi_remove(struct spi_device *spi)
> +{
> +	struct cpc_spi *ctx = spi_get_drvdata(spi);
> +	struct cpc_interface *intf = ctx->intf;
> +
> +	kthread_stop(ctx->task);
> +	free_irq(spi->irq, ctx);
> +	cpc_interface_unregister(intf);
> +	kfree(ctx);
> +}
> +
> +static const struct of_device_id cpc_dt_ids[] = {
> +	{ .compatible = "silabs,cpc-spi" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, cpc_dt_ids);
> +
> +static const struct spi_device_id cpc_spi_ids[] = {
> +	{ .name = "cpc-spi" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(spi, cpc_spi_ids);
> +
> +static struct spi_driver cpc_spi_driver = {
> +	.driver = {
> +		.name = "cpc-spi",
> +		.of_match_table = cpc_dt_ids,
> +	},

Quoting an earlier patch:

> As a very basic matching mechanism, the bus will match an endpoint
> with its driver if driver's name (driver.name attribute) matches
> endpoint's name.

Don't you want silabs in the name, so you can tell it from some other
vendors SPI bus?

	Andrew

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ