openwrt/target/linux/bcm27xx/patches-6.6/950-1165-spi-Add-a-driver-for-the-RPI-RP2040-GPIO-bridge.patch
Álvaro Fernández Rojas 0171157d45 bcm27xx: update to latest RPi patches
The patches were generated from the RPi repo with the following command:
git format-patch v6.6.44..rpi-6.6.y

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2024-08-06 18:38:00 +02:00

1289 lines
34 KiB
Diff

From 99ae83f1944f47d4338ef7a6f02536927fc6ce57 Mon Sep 17 00:00:00 2001
From: Richard Oliver <richard.oliver@raspberrypi.com>
Date: Tue, 21 May 2024 13:47:23 +0100
Subject: [PATCH 1165/1215] spi: Add a driver for the RPI RP2040 GPIO bridge
The Raspberry Pi RP2040 GPIO bridge is an I2C-attached device exposing
both a Tx-only SPI controller, and a GPIO controller.
Due to the relative difference in transfer rates between standard-mode
I2C and SPI, the GPIO bridge makes use of 12 MiB of non-volatile storage
to cache repeated transfers. This cache is arranged in ~8 KiB blocks and
is addressed by the MD5 digest of the data contained therein.
Optionally, this driver is able to take advantage of Raspberry Pi RP1
GPIOs to achieve faster than I2C data transfer rates.
Signed-off-by: Richard Oliver <richard.oliver@raspberrypi.com>
---
MAINTAINERS | 1 +
drivers/spi/Kconfig | 12 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-rp2040-gpio-bridge.c | 1219 ++++++++++++++++++++++++++
4 files changed, 1233 insertions(+)
create mode 100644 drivers/spi/spi-rp2040-gpio-bridge.c
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18031,6 +18031,7 @@ RASPBERRY PI RP2040 GPIO BRIDGE DRIVER
M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
S: Maintained
F: Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml
+F: drivers/spi/spi-rp2040-gpio-bridge.c
RAYLINK/WEBGEAR 802.11 WIRELESS LAN DRIVER
L: linux-wireless@vger.kernel.org
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -846,6 +846,18 @@ config SPI_RB4XX
help
SPI controller driver for the Mikrotik RB4xx series boards.
+config SPI_RP2040_GPIO_BRIDGE
+ tristate "Raspberry Pi RP2040 GPIO Bridge"
+ depends on I2C && SPI && GPIOLIB
+ help
+ Support for the Raspberry Pi RP2040 GPIO bridge.
+
+ This driver provides support for the Raspberry Pi PR2040 GPIO bridge.
+ It can be used as a GPIO expander and a Tx-only SPI master.
+
+ Optionally, this driver is able to take advantage of Raspberry Pi RP1
+ GPIOs to achieve faster than I2C data transfer rates.
+
config SPI_RPCIF
tristate "Renesas RPC-IF SPI driver"
depends on RENESAS_RPCIF
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -115,6 +115,7 @@ obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockc
obj-$(CONFIG_SPI_ROCKCHIP_SFC) += spi-rockchip-sfc.o
obj-$(CONFIG_SPI_RB4XX) += spi-rb4xx.o
obj-$(CONFIG_MACH_REALTEK_RTL) += spi-realtek-rtl.o
+obj-$(CONFIG_SPI_RP2040_GPIO_BRIDGE) += spi-rp2040-gpio-bridge.o
obj-$(CONFIG_SPI_RPCIF) += spi-rpc-if.o
obj-$(CONFIG_SPI_RSPI) += spi-rspi.o
obj-$(CONFIG_SPI_RZV2M_CSI) += spi-rzv2m-csi.o
--- /dev/null
+++ b/drivers/spi/spi-rp2040-gpio-bridge.c
@@ -0,0 +1,1219 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * RP2040 GPIO Bridge
+ *
+ * Copyright (C) 2023, 2024, Raspberry Pi Ltd
+ */
+
+#include <crypto/hash.h>
+#include <linux/crypto.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/minmax.h>
+#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+
+#define MODULE_NAME "rp2040-gpio-bridge"
+
+#define I2C_RETRIES 4U
+
+#define ONE_KIB 1024U
+#define MD5_SUFFIX_SIZE 9U
+
+#define RP2040_GBDG_FLASH_BLOCK_SIZE (8U * ONE_KIB)
+#define RP2040_GBDG_BLOCK_SIZE (RP2040_GBDG_FLASH_BLOCK_SIZE - MD5_SUFFIX_SIZE)
+
+/*
+ * 1MiB transfer size is an arbitrary limit
+ * Max value is 4173330 (using a single manifest)
+ */
+#define MAX_TRANSFER_SIZE (1024U * ONE_KIB)
+
+#define HALF_BUFFER (4U * ONE_KIB)
+
+#define STATUS_SIZE 4
+#define MD5_DIGEST_SIZE 16
+#define VERSION_SIZE 4
+#define ID_SIZE 8
+#define TOTAL_RD_HDR_SIZE \
+ (STATUS_SIZE + MD5_DIGEST_SIZE + VERSION_SIZE + ID_SIZE)
+
+struct rp2040_gbdg_device_info {
+ u8 md5[MD5_DIGEST_SIZE];
+ u64 id;
+ u32 version;
+ u32 status;
+};
+
+static_assert(sizeof(struct rp2040_gbdg_device_info) == TOTAL_RD_HDR_SIZE);
+
+#define MANIFEST_UNIT_SIZE 16
+static_assert(MD5_DIGEST_SIZE == MANIFEST_UNIT_SIZE);
+#define MANIFEST_HEADER_UNITS 1
+#define MANIFEST_DATA_UNITS \
+ DIV_ROUND_UP(MAX_TRANSFER_SIZE, RP2040_GBDG_BLOCK_SIZE)
+
+#define STATUS_BUSY 0x01
+
+#define DIRECT_PREFIX 0x00
+#define DIRECT_CMD_CS 0x07
+#define DIRECT_CMD_EMIT 0x08
+
+#define WRITE_DATA_PREFIX 0x80
+#define WRITE_DATA_PREFIX_SIZE 1
+
+#define FIXED_SIZE_CMD_PREFIX 0x81
+
+#define WRITE_DATA_UPPER_PREFIX 0x82
+#define WRITE_DATA_UPPER_PREFIX_SIZE 1
+
+#define NUM_GPIO 24
+
+enum rp2040_gbdg_fixed_size_commands {
+ /* 10-byte commands */
+ CMD_SAVE_CACHE = 0x07,
+ CMD_SEND_RB = 0x08,
+ CMD_GPIO_ST_CL = 0x0b,
+ CMD_GPIO_OE = 0x0c,
+ CMD_DAT_RECV = 0x0d,
+ CMD_DAT_EMIT = 0x0e,
+ /* 18-byte commands */
+ CMD_READ_CSUM = 0x11,
+ CMD_SEND_MANI = 0x13,
+};
+
+struct rp2040_gbdg {
+ struct spi_controller *controller;
+
+ struct i2c_client *client;
+ struct crypto_shash *shash;
+ struct shash_desc *shash_desc;
+
+ struct regulator *regulator;
+
+ struct gpio_chip gc;
+ u32 gpio_requested;
+ u32 gpio_direction;
+
+ bool fast_xfer_requires_i2c_lock;
+ struct gpio_descs *fast_xfer_gpios;
+ u32 fast_xfer_recv_gpio_base;
+ u8 fast_xfer_data_index;
+ u8 fast_xfer_clock_index;
+ void __iomem *gpio_base;
+ void __iomem *rio_base;
+
+ bool bypass_cache;
+
+ u8 buffer[2 + HALF_BUFFER];
+ u8 manifest_prep[(MANIFEST_HEADER_UNITS + MANIFEST_DATA_UNITS) *
+ MANIFEST_UNIT_SIZE];
+};
+
+static int rp2040_gbdg_gpio_dir_in(struct gpio_chip *gc, unsigned int offset);
+static void rp2040_gbdg_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value);
+static int rp2040_gbdg_fast_xfer(struct rp2040_gbdg *priv_data, const u8 *data,
+ size_t len);
+
+static int rp2040_gbdg_rp1_calc_offsets(u8 gpio, size_t *bank_offset,
+ u8 *shift_offset)
+{
+ if (!bank_offset || !shift_offset || gpio >= 54)
+ return -EINVAL;
+ if (gpio < 28) {
+ *bank_offset = 0x0000;
+ *shift_offset = gpio;
+ } else if (gpio < 34) {
+ *bank_offset = 0x4000;
+ *shift_offset = gpio - 28;
+ } else {
+ *bank_offset = 0x8000;
+ *shift_offset = gpio - 34;
+ }
+
+ return 0;
+}
+
+static int rp2040_gbdg_calc_mux_offset(u8 gpio, size_t *offset)
+{
+ size_t bank_offset;
+ u8 shift_offset;
+ int ret;
+
+ ret = rp2040_gbdg_rp1_calc_offsets(gpio, &bank_offset, &shift_offset);
+ if (ret)
+ return ret;
+ *offset = bank_offset + shift_offset * 8 + 0x4;
+
+ return 0;
+}
+
+static int rp2040_gbdg_rp1_read_mux(struct rp2040_gbdg *priv_data, u8 gpio,
+ u32 *data)
+{
+ size_t offset;
+ int ret;
+
+ ret = rp2040_gbdg_calc_mux_offset(gpio, &offset);
+ if (ret)
+ return ret;
+
+ *data = readl(priv_data->gpio_base + offset);
+
+ return 0;
+}
+
+static int rp2040_gbdg_rp1_write_mux(struct rp2040_gbdg *priv_data, u8 gpio,
+ u32 val)
+{
+ size_t offset;
+ int ret;
+
+ ret = rp2040_gbdg_calc_mux_offset(gpio, &offset);
+ if (ret)
+ return ret;
+
+ writel(val, priv_data->gpio_base + offset);
+
+ return 0;
+}
+
+static size_t rp2040_gbdg_max_transfer_size(struct spi_device *spi)
+{
+ return MAX_TRANSFER_SIZE;
+}
+
+static int rp2040_gbdg_get_device_info(struct i2c_client *client,
+ struct rp2040_gbdg_device_info *info)
+{
+ u8 buf[TOTAL_RD_HDR_SIZE];
+ u8 retries = I2C_RETRIES;
+ u8 *read_pos = buf;
+ size_t field_size;
+ int ret;
+
+ do {
+ ret = i2c_master_recv(client, buf, sizeof(buf));
+ if (!retries--)
+ break;
+ } while (ret == -ETIMEDOUT);
+
+ if (ret != sizeof(buf))
+ return ret < 0 ? ret : -EIO;
+
+ field_size = sizeof_field(struct rp2040_gbdg_device_info, status);
+ memcpy(&info->status, read_pos, field_size);
+ read_pos += field_size;
+
+ field_size = sizeof_field(struct rp2040_gbdg_device_info, md5);
+ memcpy(&info->md5, read_pos, field_size);
+ read_pos += field_size;
+
+ field_size = sizeof_field(struct rp2040_gbdg_device_info, version);
+ memcpy(&info->version, read_pos, field_size);
+ read_pos += field_size;
+
+ field_size = sizeof_field(struct rp2040_gbdg_device_info, id);
+ memcpy(&info->id, read_pos, field_size);
+
+ return 0;
+}
+
+static int rp2040_gbdg_poll_device_info(struct i2c_client *client,
+ struct rp2040_gbdg_device_info *info)
+{
+ struct rp2040_gbdg_device_info itnl;
+ int ret;
+
+ itnl.status = STATUS_BUSY;
+
+ while (itnl.status & STATUS_BUSY) {
+ ret = rp2040_gbdg_get_device_info(client, &itnl);
+ if (ret)
+ return ret;
+ }
+ memcpy(info, &itnl, sizeof(itnl));
+
+ return 0;
+}
+
+static int rp2040_gbdg_get_buffer_hash(struct i2c_client *client, u8 *md5)
+{
+ struct rp2040_gbdg_device_info info;
+ int ret;
+
+ ret = rp2040_gbdg_poll_device_info(client, &info);
+ if (ret)
+ return ret;
+
+ memcpy(md5, info.md5, MD5_DIGEST_SIZE);
+
+ return 0;
+}
+
+static int rp2040_gbdg_wait_until_free(struct i2c_client *client, u8 *status)
+{
+ struct rp2040_gbdg_device_info info;
+ int ret;
+
+ ret = rp2040_gbdg_poll_device_info(client, &info);
+ if (ret)
+ return ret;
+
+ if (status)
+ *status = info.status;
+
+ return 0;
+}
+
+static int rp2040_gbdg_i2c_send(struct i2c_client *client, const u8 *buf,
+ size_t len)
+{
+ u8 retries = I2C_RETRIES;
+ int ret;
+
+ ret = rp2040_gbdg_wait_until_free(client, NULL);
+ if (ret) {
+ dev_err(&client->dev,
+ "%s() rp2040_gbdg_wait_until_free failed\n", __func__);
+ return ret;
+ }
+
+ do {
+ ret = i2c_master_send(client, buf, len);
+ if (!retries--)
+ break;
+ } while (ret == -ETIMEDOUT);
+
+ if (ret != len) {
+ dev_err(&client->dev, "%s() i2c_master_send returned %d\n",
+ __func__, ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ return 0;
+}
+
+static int rp2040_gbdg_10byte_cmd(struct i2c_client *client, u8 cmd, u32 addr,
+ u32 len)
+{
+ u8 buffer[10];
+
+ buffer[0] = FIXED_SIZE_CMD_PREFIX;
+ buffer[1] = cmd;
+ memcpy(&buffer[2], &addr, sizeof(addr));
+ memcpy(&buffer[6], &len, sizeof(len));
+
+ return rp2040_gbdg_i2c_send(client, buffer, sizeof(buffer));
+}
+
+static int rp2040_gbdg_18byte_cmd(struct i2c_client *client, u8 cmd,
+ const u8 *digest)
+{
+ u8 buffer[18];
+
+ buffer[0] = FIXED_SIZE_CMD_PREFIX;
+ buffer[1] = cmd;
+ memcpy(&buffer[2], digest, MD5_DIGEST_SIZE);
+
+ return rp2040_gbdg_i2c_send(client, buffer, sizeof(buffer));
+}
+
+static int rp2040_gbdg_block_hash(struct rp2040_gbdg *priv_data, const u8 *data,
+ size_t len, u8 *out)
+{
+ size_t remaining = RP2040_GBDG_BLOCK_SIZE;
+ size_t pad;
+ int ret;
+
+ static const u8 padding[64] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ };
+
+ if (len > RP2040_GBDG_BLOCK_SIZE) {
+ return -EMSGSIZE;
+ } else if (len == RP2040_GBDG_BLOCK_SIZE) {
+ return crypto_shash_digest(priv_data->shash_desc, data, len,
+ out);
+ } else {
+ ret = crypto_shash_init(priv_data->shash_desc);
+ if (ret)
+ return ret;
+
+ ret = crypto_shash_update(priv_data->shash_desc, data, len);
+ if (ret)
+ return ret;
+ remaining -= len;
+
+ /* Pad up-to a 64-byte boundary, unless that takes us over. */
+ pad = round_up(len, 64);
+ if (pad != len && pad < RP2040_GBDG_BLOCK_SIZE) {
+ ret = crypto_shash_update(priv_data->shash_desc,
+ padding, pad - len);
+ if (ret)
+ return ret;
+ remaining -= (pad - len);
+ }
+
+ /* Pad up-to RP2040_GBDG_BLOCK_SIZE in, preferably, 64-byte chunks */
+ while (remaining) {
+ pad = min_t(size_t, remaining, (size_t)64U);
+ ret = crypto_shash_update(priv_data->shash_desc,
+ padding, pad);
+ if (ret)
+ return ret;
+ remaining -= pad;
+ }
+ return crypto_shash_final(priv_data->shash_desc, out);
+ }
+}
+
+static int rp2040_gbdg_set_remote_buffer_fast(struct rp2040_gbdg *priv_data,
+ const u8 *data, unsigned int len)
+{
+ struct i2c_client *client = priv_data->client;
+ int ret;
+
+ if (len > RP2040_GBDG_BLOCK_SIZE)
+ return -EMSGSIZE;
+ if (!priv_data->fast_xfer_gpios)
+ return -EIO;
+
+ ret = rp2040_gbdg_10byte_cmd(client, CMD_DAT_RECV,
+ priv_data->fast_xfer_recv_gpio_base, len);
+ if (ret) {
+ dev_err(&client->dev, "%s() failed to enter fast data mode\n",
+ __func__);
+ return ret;
+ }
+
+ return rp2040_gbdg_fast_xfer(priv_data, data, len);
+}
+
+static int rp2040_gbdg_set_remote_buffer_i2c(struct rp2040_gbdg *priv_data,
+ const u8 *data, unsigned int len)
+{
+ struct i2c_client *client = priv_data->client;
+ unsigned int write_len;
+ int ret;
+
+ if (len > RP2040_GBDG_BLOCK_SIZE)
+ return -EMSGSIZE;
+
+ priv_data->buffer[0] = WRITE_DATA_PREFIX;
+ write_len = min(len, HALF_BUFFER);
+ memcpy(&priv_data->buffer[1], data, write_len);
+
+ ret = rp2040_gbdg_i2c_send(client, priv_data->buffer, write_len + 1);
+ if (ret)
+ return ret;
+
+ len -= write_len;
+ data += write_len;
+
+ if (!len)
+ return 0;
+
+ priv_data->buffer[0] = WRITE_DATA_UPPER_PREFIX;
+ memcpy(&priv_data->buffer[1], data, len);
+ ret = rp2040_gbdg_i2c_send(client, priv_data->buffer, len + 1);
+
+ return ret;
+}
+
+static int rp2040_gbdg_set_remote_buffer(struct rp2040_gbdg *priv_data,
+ const u8 *data, unsigned int len)
+{
+ if (priv_data->fast_xfer_gpios)
+ return rp2040_gbdg_set_remote_buffer_fast(priv_data, data, len);
+ else
+ return rp2040_gbdg_set_remote_buffer_i2c(priv_data, data, len);
+}
+
+/* Loads data by checksum if available or resorts to sending byte-by-byte */
+static int rp2040_gbdg_load_block_remote(struct rp2040_gbdg *priv_data,
+ const void *data, unsigned int len,
+ u8 *digest, bool persist)
+{
+ u8 ascii_digest[MD5_DIGEST_SIZE * 2 + 1] = { 0 };
+ struct i2c_client *client = priv_data->client;
+ u8 remote_digest[MD5_DIGEST_SIZE];
+ u8 local_digest[MD5_DIGEST_SIZE];
+ int ret;
+
+ if (len > RP2040_GBDG_BLOCK_SIZE)
+ return -EMSGSIZE;
+
+ ret = rp2040_gbdg_block_hash(priv_data, data, len, local_digest);
+ if (ret)
+ return ret;
+
+ if (digest)
+ memcpy(digest, local_digest, MD5_DIGEST_SIZE);
+
+ /* Check if the RP2040 has the data already */
+ ret = rp2040_gbdg_18byte_cmd(client, CMD_READ_CSUM, local_digest);
+ if (ret)
+ return ret;
+
+ ret = rp2040_gbdg_get_buffer_hash(client, remote_digest);
+ if (ret)
+ return ret;
+
+ if (memcmp(local_digest, remote_digest, MD5_DIGEST_SIZE)) {
+ bin2hex(ascii_digest, local_digest, MD5_DIGEST_SIZE);
+ dev_info(&client->dev, "%s() device missing data: %s\n",
+ __func__, ascii_digest);
+ /*
+ * N.B. We're fine to send (the potentially shorter) transfer->len
+ * number of bytes here as the RP2040 will pad with 0xFF up to buffer
+ * size once we stop sending.
+ */
+ ret = rp2040_gbdg_set_remote_buffer(priv_data, data, len);
+ if (ret)
+ return ret;
+
+ /* Make sure the data actually arrived. */
+ ret = rp2040_gbdg_get_buffer_hash(client, remote_digest);
+ if (memcmp(local_digest, remote_digest, MD5_DIGEST_SIZE)) {
+ dev_err(&priv_data->client->dev,
+ "%s() unable to send data to device\n",
+ __func__);
+ return -EREMOTEIO;
+ }
+
+ if (persist) {
+ dev_info(&client->dev,
+ "%s() sent missing data to device, saving\n",
+ __func__);
+ ret = rp2040_gbdg_10byte_cmd(client, CMD_SAVE_CACHE, 0,
+ 0);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int rp2040_gbdg_transfer_block(struct rp2040_gbdg *priv_data,
+ const void *data, unsigned int len)
+{
+ struct i2c_client *client = priv_data->client;
+ int ret;
+
+ if (len > RP2040_GBDG_BLOCK_SIZE)
+ return -EMSGSIZE;
+
+ ret = rp2040_gbdg_load_block_remote(priv_data, data, len, NULL, true);
+ if (ret)
+ return ret;
+
+ /* Remote rambuffer now has correct contents, send it */
+ ret = rp2040_gbdg_10byte_cmd(client, CMD_SEND_RB, 0, len);
+ if (ret)
+ return ret;
+
+ /*
+ * Wait for data to have actually completed sending as we may be de-asserting CS too quickly
+ * otherwise.
+ */
+ ret = rp2040_gbdg_wait_until_free(client, NULL);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int rp2040_gbdg_transfer_manifest(struct rp2040_gbdg *priv_data,
+ const u8 *data, unsigned int len)
+{
+ struct i2c_client *client = priv_data->client;
+ static const char magic[] = "DATA_MANFST";
+ unsigned int remaining = len;
+ const u32 data_length = len;
+ u8 digest[MD5_DIGEST_SIZE];
+ u8 *digest_write_pos;
+ u8 status;
+ int ret;
+
+ memcpy(priv_data->manifest_prep, magic, sizeof(magic));
+ memcpy(priv_data->manifest_prep + sizeof(magic), &data_length,
+ sizeof(data_length));
+ digest_write_pos =
+ priv_data->manifest_prep + sizeof(magic) + sizeof(data_length);
+
+ while (remaining) {
+ unsigned int size = min(remaining, RP2040_GBDG_BLOCK_SIZE);
+
+ ret = rp2040_gbdg_block_hash(priv_data, data, size,
+ digest_write_pos);
+ if (ret)
+ return ret;
+
+ remaining -= size;
+ data += size;
+ digest_write_pos += MD5_DIGEST_SIZE;
+ }
+
+ ret = rp2040_gbdg_load_block_remote(
+ priv_data, priv_data->manifest_prep,
+ digest_write_pos - priv_data->manifest_prep, digest, true);
+ if (ret)
+ return ret;
+
+ dev_info(&client->dev, "%s() issue CMD_SEND_MANI\n", __func__);
+ ret = rp2040_gbdg_18byte_cmd(client, CMD_SEND_MANI, digest);
+ if (ret)
+ return ret;
+
+ ret = rp2040_gbdg_wait_until_free(client, &status);
+ if (ret)
+ return ret;
+
+ dev_info(&client->dev, "%s() SEND_MANI response: %02x\n", __func__,
+ status);
+
+ return status;
+}
+
+/* Precondition: correctly initialised fast_xfer_*, gpio_base, rio_base */
+static int rp2040_gbdg_fast_xfer(struct rp2040_gbdg *priv_data, const u8 *data,
+ size_t len)
+{
+ struct i2c_client *client = priv_data->client;
+ void __iomem *clock_toggle;
+ void __iomem *data_set;
+ size_t clock_bank;
+ size_t data_bank;
+ u8 clock_offset;
+ u8 data_offset;
+ u32 clock_mux;
+ u32 data_mux;
+
+ if (priv_data->fast_xfer_requires_i2c_lock)
+ i2c_lock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+
+ rp2040_gbdg_rp1_read_mux(priv_data, priv_data->fast_xfer_data_index,
+ &data_mux);
+ rp2040_gbdg_rp1_read_mux(priv_data, priv_data->fast_xfer_clock_index,
+ &clock_mux);
+
+ gpiod_direction_output(priv_data->fast_xfer_gpios->desc[0], 1);
+ gpiod_direction_output(priv_data->fast_xfer_gpios->desc[1], 0);
+
+ rp2040_gbdg_rp1_calc_offsets(priv_data->fast_xfer_data_index,
+ &data_bank, &data_offset);
+ rp2040_gbdg_rp1_calc_offsets(priv_data->fast_xfer_clock_index,
+ &clock_bank, &clock_offset);
+
+ data_set = priv_data->rio_base + data_bank + 0x2000; /* SET offset */
+ clock_toggle =
+ priv_data->rio_base + clock_bank + 0x1000; /* XOR offset */
+
+ while (len--) {
+ /* MSB first ordering */
+ u32 d = ~(*data++) << 4U;
+ /*
+ * Clock out each bit of data, LSB first
+ * (DDR, achieves approx 5 Mbps)
+ */
+ for (size_t i = 0; i < 8; i++) {
+ /* Branchless set/clr data */
+ writel(1 << data_offset,
+ data_set + ((d <<= 1) & 0x1000) /* CLR offset */
+ );
+
+ /* Toggle the clock */
+ writel(1 << clock_offset, clock_toggle);
+ }
+ }
+
+ rp2040_gbdg_rp1_write_mux(priv_data, priv_data->fast_xfer_data_index,
+ data_mux);
+ rp2040_gbdg_rp1_write_mux(priv_data, priv_data->fast_xfer_clock_index,
+ clock_mux);
+
+ if (priv_data->fast_xfer_requires_i2c_lock)
+ i2c_unlock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+
+ return 0;
+}
+
+static int rp2040_gbdg_transfer_bypass(struct rp2040_gbdg *priv_data,
+ const u8 *data, unsigned int length)
+{
+ int ret;
+ u8 *buf;
+
+ if (priv_data->fast_xfer_gpios) {
+ ret = rp2040_gbdg_10byte_cmd(
+ priv_data->client, CMD_DAT_EMIT,
+ priv_data->fast_xfer_recv_gpio_base, length);
+ return ret ? ret :
+ rp2040_gbdg_fast_xfer(priv_data, data, length);
+ }
+
+ buf = priv_data->buffer;
+
+ while (length) {
+ unsigned int xfer = min(length, HALF_BUFFER);
+
+ buf[0] = DIRECT_PREFIX;
+ buf[1] = DIRECT_CMD_EMIT;
+ memcpy(&buf[2], data, xfer);
+ ret = rp2040_gbdg_i2c_send(priv_data->client, buf, xfer + 2);
+ if (ret)
+ return ret;
+ length -= xfer;
+ data += xfer;
+ }
+
+ return 0;
+}
+
+static int rp2040_gbdg_transfer_cached(struct rp2040_gbdg *priv_data,
+ const u8 *data, unsigned int length)
+{
+ int ret;
+
+ /*
+ * Caching mechanism divides data into '8KiB - 9' (8183 byte)
+ * 'RP2040_GBDG_BLOCK_SIZE' blocks.
+ *
+ * If there's a large amount of data to send, instead, attempt to make use
+ * of a manifest.
+ */
+ if (length > (2 * RP2040_GBDG_BLOCK_SIZE)) {
+ if (!rp2040_gbdg_transfer_manifest(priv_data, data, length))
+ return 0;
+ }
+
+ while (length) {
+ unsigned int xfer = min(length, RP2040_GBDG_BLOCK_SIZE);
+
+ ret = rp2040_gbdg_transfer_block(priv_data, data, xfer);
+ if (ret)
+ return ret;
+ length -= xfer;
+ data += xfer;
+ }
+
+ return 0;
+}
+
+static int rp2040_gbdg_transfer_one(struct spi_controller *ctlr,
+ struct spi_device *spi,
+ struct spi_transfer *transfer)
+{
+ /* All transfers are performed in a synchronous manner. As such, return '0'
+ * on success or -ve on failure. (Returning +ve indicates async xfer)
+ */
+
+ struct rp2040_gbdg *priv_data = spi_controller_get_devdata(ctlr);
+
+ if (priv_data->bypass_cache) {
+ return rp2040_gbdg_transfer_bypass(priv_data, transfer->tx_buf,
+ transfer->len);
+ } else {
+ return rp2040_gbdg_transfer_cached(priv_data, transfer->tx_buf,
+ transfer->len);
+ }
+}
+
+static void rp2040_gbdg_set_cs(struct spi_device *spi, bool enable)
+{
+ static const char disable_cs[] = { DIRECT_PREFIX, DIRECT_CMD_CS, 0x00 };
+ static const char enable_cs[] = { DIRECT_PREFIX, DIRECT_CMD_CS, 0x10 };
+ struct rp2040_gbdg *p_data;
+
+ p_data = spi_controller_get_devdata(spi->controller);
+
+ /*
+ * 'enable' is inverted and instead describes the logic level of an
+ * active-low CS.
+ */
+ rp2040_gbdg_i2c_send(p_data->client, enable ? disable_cs : enable_cs,
+ 3);
+}
+
+static int rp2040_gbdg_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+ u32 pattern;
+ int ret;
+
+ if (offset >= NUM_GPIO)
+ return -EINVAL;
+
+ pattern = (1 << (offset + 8));
+ if (pattern & priv_data->gpio_requested)
+ return -EBUSY;
+
+ /* Resume if previously no gpio requested */
+ if (!priv_data->gpio_requested) {
+ ret = pm_runtime_resume_and_get(&priv_data->client->dev);
+ if (ret) {
+ dev_err(&priv_data->client->dev,
+ "%s(%u) unable to resume\n", __func__, offset);
+ return ret;
+ }
+ }
+
+ priv_data->gpio_requested |= pattern;
+
+ return 0;
+}
+
+static void rp2040_gbdg_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+ u32 pattern;
+ int ret;
+
+ if (offset >= NUM_GPIO || !priv_data->gpio_requested)
+ return;
+
+ pattern = (1 << (offset + 8));
+
+ priv_data->gpio_requested &= ~pattern;
+ rp2040_gbdg_gpio_dir_in(gc, offset);
+ rp2040_gbdg_gpio_set(gc, offset, 0);
+
+ if (!priv_data->gpio_requested) {
+ ret = pm_runtime_put_autosuspend(&priv_data->client->dev);
+ if (ret) {
+ dev_err(&priv_data->client->dev,
+ "%s(%u) unable to put_autosuspend\n", __func__,
+ offset);
+ }
+ }
+}
+
+static int rp2040_gbdg_gpio_get_direction(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+
+ if (offset >= NUM_GPIO)
+ return -EINVAL;
+
+ return (priv_data->gpio_direction & (1 << (offset + 8))) ?
+ GPIO_LINE_DIRECTION_IN :
+ GPIO_LINE_DIRECTION_OUT;
+}
+
+static int rp2040_gbdg_gpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+ struct i2c_client *client = priv_data->client;
+
+ if (offset >= NUM_GPIO)
+ return -EINVAL;
+
+ priv_data->gpio_direction |= (1 << (offset + 8));
+
+ return rp2040_gbdg_10byte_cmd(client, CMD_GPIO_OE,
+ ~priv_data->gpio_direction,
+ priv_data->gpio_direction);
+}
+
+static int rp2040_gbdg_gpio_dir_out(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+ struct i2c_client *client = priv_data->client;
+ u32 pattern;
+ int ret;
+
+ if (offset >= NUM_GPIO)
+ return -EINVAL;
+
+ pattern = (1 << (offset + 8));
+
+ ret = rp2040_gbdg_10byte_cmd(client, CMD_GPIO_ST_CL,
+ value ? pattern : 0, !value ? pattern : 0);
+ if (ret) {
+ dev_err(&client->dev, "%s(%u, %d) could not ST_CL\n", __func__,
+ offset, value);
+ return ret;
+ }
+
+ priv_data->gpio_direction &= ~pattern;
+ ret = rp2040_gbdg_10byte_cmd(client, CMD_GPIO_OE,
+ ~priv_data->gpio_direction,
+ priv_data->gpio_direction);
+
+ return ret;
+}
+
+static int rp2040_gbdg_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+ struct i2c_client *client = priv_data->client;
+ struct rp2040_gbdg_device_info info;
+ int ret;
+
+ if (offset >= NUM_GPIO)
+ return -EINVAL;
+
+ ret = rp2040_gbdg_get_device_info(client, &info);
+ if (ret)
+ return ret;
+
+ return info.status & (1 << (offset + 8)) ? 1 : 0;
+}
+
+static void rp2040_gbdg_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+ struct i2c_client *client = priv_data->client;
+ u32 pattern;
+
+ if (offset >= NUM_GPIO)
+ return;
+
+ pattern = (1 << (offset + 8));
+ rp2040_gbdg_10byte_cmd(client, CMD_GPIO_ST_CL, value ? pattern : 0,
+ !value ? pattern : 0);
+}
+
+static int rp2040_gbdg_get_regulator(struct device *dev,
+ struct rp2040_gbdg *rp2040_gbdg)
+{
+ struct regulator *reg = devm_regulator_get(dev, "power");
+
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ rp2040_gbdg->regulator = reg;
+
+ return 0;
+}
+
+static void rp2040_gbdg_parse_dt(struct rp2040_gbdg *rp2040_gbdg)
+{
+ struct i2c_client *client = rp2040_gbdg->client;
+ struct of_phandle_args of_args[2] = { 0 };
+ struct device *dev = &client->dev;
+ struct device_node *dn;
+
+ rp2040_gbdg->bypass_cache =
+ of_property_read_bool(client->dev.of_node, "bypass-cache");
+
+ /* Optionally configure fast_xfer if RP1 is being used */
+ if (of_parse_phandle_with_args(client->dev.of_node, "fast_xfer-gpios",
+ "#gpio-cells", 0, &of_args[0]) ||
+ of_parse_phandle_with_args(client->dev.of_node, "fast_xfer-gpios",
+ "#gpio-cells", 1, &of_args[1])) {
+ dev_info(dev, "Could not parse fast_xfer-gpios phandles\n");
+ goto node_put;
+ }
+
+ if (of_args[0].np != of_args[1].np) {
+ dev_info(
+ dev,
+ "fast_xfer-gpios are not provided by the same controller\n");
+ goto node_put;
+ }
+ dn = of_args[0].np;
+ if (!of_device_is_compatible(dn, "raspberrypi,rp1-gpio")) {
+ dev_info(dev, "fast_xfer-gpios controller is not an rp1\n");
+ goto node_put;
+ }
+ if (of_args[0].args_count != 2 || of_args[1].args_count != 2) {
+ dev_info(dev, "of_args count is %d\n", of_args[0].args_count);
+ goto node_put;
+ }
+
+ if (of_property_read_u32_index(
+ client->dev.of_node, "fast_xfer_recv_gpio_base", 0,
+ &rp2040_gbdg->fast_xfer_recv_gpio_base)) {
+ dev_info(dev, "Could not read fast_xfer_recv_gpio_base\n");
+ goto node_put;
+ }
+
+ rp2040_gbdg->fast_xfer_gpios =
+ devm_gpiod_get_array_optional(dev, "fast_xfer", GPIOD_ASIS);
+ if (!rp2040_gbdg->fast_xfer_gpios) {
+ dev_info(dev, "Could not acquire fast_xfer-gpios\n");
+ goto node_put;
+ }
+
+ rp2040_gbdg->fast_xfer_data_index = of_args[0].args[0];
+ rp2040_gbdg->fast_xfer_clock_index = of_args[1].args[0];
+ rp2040_gbdg->fast_xfer_requires_i2c_lock = of_property_read_bool(
+ client->dev.of_node, "fast_xfer_requires_i2c_lock");
+
+ rp2040_gbdg->gpio_base = of_iomap(dn, 0);
+ if (IS_ERR_OR_NULL(rp2040_gbdg->gpio_base)) {
+ dev_info(&client->dev, "%s() unable to map gpio_base\n",
+ __func__);
+ rp2040_gbdg->gpio_base = NULL;
+ devm_gpiod_put_array(dev, rp2040_gbdg->fast_xfer_gpios);
+ rp2040_gbdg->fast_xfer_gpios = NULL;
+ goto node_put;
+ }
+
+ rp2040_gbdg->rio_base = of_iomap(dn, 1);
+ if (IS_ERR_OR_NULL(rp2040_gbdg->rio_base)) {
+ dev_info(&client->dev, "%s() unable to map rio_base\n",
+ __func__);
+ rp2040_gbdg->rio_base = NULL;
+ iounmap(rp2040_gbdg->gpio_base);
+ rp2040_gbdg->gpio_base = NULL;
+ devm_gpiod_put_array(dev, rp2040_gbdg->fast_xfer_gpios);
+ rp2040_gbdg->fast_xfer_gpios = NULL;
+ goto node_put;
+ }
+
+node_put:
+ if (of_args[0].np)
+ of_node_put(of_args[0].np);
+ if (of_args[1].np)
+ of_node_put(of_args[1].np);
+}
+
+static int rp2040_gbdg_power_off(struct rp2040_gbdg *rp2040_gbdg)
+{
+ struct device *dev = &rp2040_gbdg->client->dev;
+ int ret;
+
+ ret = regulator_disable(rp2040_gbdg->regulator);
+ if (ret) {
+ dev_err(dev, "%s: Could not disable regulator\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rp2040_gbdg_power_on(struct rp2040_gbdg *rp2040_gbdg)
+{
+ struct device *dev = &rp2040_gbdg->client->dev;
+ int ret;
+
+ ret = regulator_enable(rp2040_gbdg->regulator);
+ if (ret) {
+ dev_err(dev, "%s: Could not enable regulator\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rp2040_gbdg_probe(struct i2c_client *client)
+{
+ struct rp2040_gbdg_device_info info;
+ struct spi_controller *controller;
+ struct device *dev = &client->dev;
+ struct rp2040_gbdg *rp2040_gbdg;
+ struct device_node *np;
+ int ret;
+
+ np = dev->of_node;
+
+ controller = devm_spi_alloc_master(dev, sizeof(struct rp2040_gbdg));
+ if (!controller)
+ return dev_err_probe(dev, ENOMEM,
+ "could not alloc spi controller\n");
+
+ rp2040_gbdg = spi_controller_get_devdata(controller);
+ i2c_set_clientdata(client, rp2040_gbdg);
+ rp2040_gbdg->controller = controller;
+ rp2040_gbdg->client = client;
+
+ ret = rp2040_gbdg_get_regulator(dev, rp2040_gbdg);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot get regulator\n");
+
+ ret = rp2040_gbdg_power_on(rp2040_gbdg);
+ if (ret)
+ return dev_err_probe(dev, ret, "Could not power on device\n");
+
+ pm_runtime_set_active(dev);
+ pm_runtime_get_noresume(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, 1000);
+ pm_runtime_use_autosuspend(dev);
+
+ ret = rp2040_gbdg_get_device_info(client, &info);
+ if (ret) {
+ dev_err(dev, "Could not get device info\n");
+ goto err_pm;
+ }
+
+ dev_info(dev, "%s() found dev ID: %llx, fw ver. %u\n", __func__,
+ info.id, info.version);
+
+ rp2040_gbdg->shash = crypto_alloc_shash("md5", 0, 0);
+ if (IS_ERR(rp2040_gbdg->shash)) {
+ ret = PTR_ERR(rp2040_gbdg->shash);
+ dev_err(dev, "Could not allocate shash\n");
+ goto err_pm;
+ }
+
+ if (crypto_shash_digestsize(rp2040_gbdg->shash) != MD5_DIGEST_SIZE) {
+ ret = -EINVAL;
+ dev_err(dev, "error: Unexpected hash digest size\n");
+ goto err_shash;
+ }
+
+ rp2040_gbdg->shash_desc =
+ devm_kmalloc(dev,
+ sizeof(struct shash_desc) +
+ crypto_shash_descsize(rp2040_gbdg->shash),
+ 0);
+
+ if (!rp2040_gbdg->shash_desc) {
+ ret = -ENOMEM;
+ dev_err(dev,
+ "error: Could not allocate memory for shash_desc\n");
+ goto err_shash;
+ }
+ rp2040_gbdg->shash_desc->tfm = rp2040_gbdg->shash;
+
+ controller->bus_num = -1;
+ controller->num_chipselect = 1;
+ controller->mode_bits = SPI_CPOL | SPI_CPHA;
+ controller->bits_per_word_mask = SPI_BPW_MASK(8);
+ controller->min_speed_hz = 35000000;
+ controller->max_speed_hz = 35000000;
+ controller->max_transfer_size = rp2040_gbdg_max_transfer_size;
+ controller->max_message_size = rp2040_gbdg_max_transfer_size;
+ controller->transfer_one = rp2040_gbdg_transfer_one;
+ controller->set_cs = rp2040_gbdg_set_cs;
+
+ controller->dev.of_node = np;
+ controller->auto_runtime_pm = true;
+
+ ret = devm_spi_register_controller(dev, controller);
+ if (ret) {
+ dev_err(dev, "error: Could not register SPI controller\n");
+ goto err_shash;
+ }
+
+ memset(&rp2040_gbdg->gc, 0, sizeof(struct gpio_chip));
+ rp2040_gbdg->gc.parent = dev;
+ rp2040_gbdg->gc.label = MODULE_NAME;
+ rp2040_gbdg->gc.owner = THIS_MODULE;
+ rp2040_gbdg->gc.base = -1;
+ rp2040_gbdg->gc.ngpio = NUM_GPIO;
+
+ rp2040_gbdg->gc.request = rp2040_gbdg_gpio_request;
+ rp2040_gbdg->gc.free = rp2040_gbdg_gpio_free;
+ rp2040_gbdg->gc.get_direction = rp2040_gbdg_gpio_get_direction;
+ rp2040_gbdg->gc.direction_input = rp2040_gbdg_gpio_dir_in;
+ rp2040_gbdg->gc.direction_output = rp2040_gbdg_gpio_dir_out;
+ rp2040_gbdg->gc.get = rp2040_gbdg_gpio_get;
+ rp2040_gbdg->gc.set = rp2040_gbdg_gpio_set;
+ rp2040_gbdg->gc.can_sleep = true;
+
+ rp2040_gbdg->gpio_requested = 0;
+
+ /* Coming out of reset, all GPIOs are inputs */
+ rp2040_gbdg->gpio_direction = ~0;
+
+ ret = devm_gpiochip_add_data(dev, &rp2040_gbdg->gc, rp2040_gbdg);
+ if (ret) {
+ dev_err(dev, "error: Could not add data to gpiochip\n");
+ goto err_shash;
+ }
+
+ rp2040_gbdg_parse_dt(rp2040_gbdg);
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+
+err_shash:
+ crypto_free_shash(rp2040_gbdg->shash);
+err_pm:
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+ rp2040_gbdg_power_off(rp2040_gbdg);
+
+ return ret;
+}
+
+static void rp2040_gbdg_remove(struct i2c_client *client)
+{
+ struct rp2040_gbdg *priv_data = i2c_get_clientdata(client);
+
+ crypto_free_shash(priv_data->shash);
+
+ if (priv_data->gpio_base) {
+ iounmap(priv_data->gpio_base);
+ priv_data->gpio_base = NULL;
+ }
+ if (priv_data->rio_base) {
+ iounmap(priv_data->rio_base);
+ priv_data->rio_base = NULL;
+ }
+
+ pm_runtime_disable(&client->dev);
+ if (!pm_runtime_status_suspended(&client->dev))
+ rp2040_gbdg_power_off(priv_data);
+ pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct i2c_device_id rp2040_gbdg_id[] = {
+ { "rp2040-gpio-bridge", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, rp2040_gbdg_id);
+
+static const struct of_device_id rp2040_gbdg_of_match[] = {
+ { .compatible = "raspberrypi,rp2040-gpio-bridge" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rp2040_gbdg_of_match);
+
+static int rp2040_gbdg_runtime_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return rp2040_gbdg_power_off(i2c_get_clientdata(client));
+}
+
+static int rp2040_gbdg_runtime_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return rp2040_gbdg_power_on(i2c_get_clientdata(client));
+}
+
+static const struct dev_pm_ops rp2040_gbdg_pm_ops = { SET_RUNTIME_PM_OPS(
+ rp2040_gbdg_runtime_suspend, rp2040_gbdg_runtime_resume, NULL) };
+
+static struct i2c_driver rp2040_gbdg_driver = {
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(rp2040_gbdg_of_match),
+ .pm = &rp2040_gbdg_pm_ops,
+ },
+ .probe = rp2040_gbdg_probe,
+ .remove = rp2040_gbdg_remove,
+ .id_table = rp2040_gbdg_id,
+};
+
+module_i2c_driver(rp2040_gbdg_driver);
+
+MODULE_AUTHOR("Richard Oliver <richard.oliver@raspberrypi.com>");
+MODULE_DESCRIPTION("Raspberry Pi RP2040 GPIO Bridge");
+MODULE_LICENSE("GPL");