Commit 64643293 authored by Tom Burdick's avatar Tom Burdick Committed by Alberto Escolar
Browse files

pmci: mctp: I2C+GPIO controller bindings



Adds a custom MCTP binding for an I2C bus controller using GPIO signaling
for write requests rather than mode switching.

This binding operates a lot like the I3C binding specification DMTF has
for MCTP. The controller expects to receive interrupts (from GPIO pins)
and upon getting an interrupt read a message from the I2C target device.

The macro does a lot of the heavy lifting to setup all the state needed
for capturing GPIOs, being able to do asynchronous reads/writes, and
such. The entire controller works using state machines driven by
interrupts leading to low latency and clear ram costs.

Signed-off-by: default avatarTom Burdick <thomas.burdick@intel.com>
parent e3cc7a8c
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
# Copyright (c) 2025 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

description: |
  A configuration for MCTP bindings to an I2C controller with GPIO.

compatible: "zephyr,mctp-i2c-gpio-controller"

properties:
  i2c:
    type: phandle
    description: |
      I2C controller that is used as an MCTP bus.

  endpoint-ids:
    type: array
    description: |
      Set of matching MCTP endpoint ids

  endpoint-addrs:
    type: array
    description: |
      I2C addresses of endpoints

  endpoint-gpios:
    type: phandle-array
    description: |
      Set of GPIOs connected to the endpoints, positionally must match endpoint-mapping.
+25 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 */

#ifndef ZEPHYR_MCTP_I2C_GPIO_COMMON_H_
#define ZEPHYR_MCTP_I2C_GPIO_COMMON_H_

/** @cond INTERNAL_HIDDEN */

/* Pseudo Register Addresses */
#define MCTP_I2C_GPIO_INVALID_ADDR         0
#define MCTP_I2C_GPIO_RX_MSG_LEN_ADDR      1
#define MCTP_I2C_GPIO_RX_MSG_ADDR          2
#define MCTP_I2C_GPIO_TX_MSG_LEN_ADDR      3
#define MCTP_I2C_GPIO_TX_MSG_ADDR          4

/* Max packet size (pseudo registers are byte sized) */
#define MCTP_I2C_GPIO_MAX_PKT_SIZE 255

/** @endcond INTERNAL_HIDDEN */

#endif /* ZEPHYR_MCTP_I2C_GPIO_COMMON_H_ */
+154 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 */

#ifndef ZEPHYR_MCTP_I2C_GPIO_CONTROLLER_H_
#define ZEPHYR_MCTP_I2C_GPIO_CONTROLLER_H_

#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/mpsc_lockfree.h>
#include <zephyr/rtio/rtio.h>
#include <zephyr/pmci/mctp/mctp_i2c_gpio_common.h>

#include <libmctp.h>
#include <stdatomic.h>

/** @cond INTERNAL_HIDDEN */

struct mctp_binding_i2c_gpio_controller;

struct mctp_i2c_gpio_controller_cb {
	struct mpsc_node q;
	struct gpio_callback callback;
	struct mctp_binding_i2c_gpio_controller *binding;
	uint8_t index;
};

/** @endcond INTERNAL_HIDDEN */


/**
 * @brief An MCTP binding for Zephyr's I2C interface using GPIO
 */
struct mctp_binding_i2c_gpio_controller {
	/** @cond INTERNAL_HIDDEN */
	struct mctp_binding binding;
	const struct device *i2c;

	uint8_t rx_buf_len;
	uint8_t rx_buf[MCTP_I2C_GPIO_MAX_PKT_SIZE];
	uint8_t tx_buf[MCTP_I2C_GPIO_MAX_PKT_SIZE];

	struct mctp_pktbuf *rx_pkt;

	/* Avoid needing a thread or work queue task by using RTIO */
	struct k_sem *tx_lock;
	struct rtio *r_tx;

	/* Mapping of endpoints to i2c targets with gpios */
	size_t num_endpoints;
	const uint8_t *endpoint_ids;
	const struct rtio_iodev **endpoint_iodevs;
	const struct gpio_dt_spec *endpoint_gpios;
	struct mctp_i2c_gpio_controller_cb *endpoint_gpio_cbs;

	/* Ongoing request from a particular gpio */
	struct k_spinlock rx_lock;
	struct rtio *r_rx;
	struct mpsc rx_q;
	struct mctp_i2c_gpio_controller_cb *inflight_rx;

	/** @endcond INTERNAL_HIDDEN */
};

/** @cond INTERNAL_HIDDEN */
int mctp_i2c_gpio_controller_start(struct mctp_binding *binding);
int mctp_i2c_gpio_controller_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt);

#define MCTP_I2C_GPIO_CONTROLLER_IODEV_NAME(_idx, _name) _name##_idx

#define MCTP_I2C_GPIO_CONTROLLER_IODEV_DEFINE(_node_id, addrs, _idx, _name)                        \
	I2C_IODEV_DEFINE(MCTP_I2C_GPIO_CONTROLLER_IODEV_NAME(_idx, _name),                         \
		DT_PHANDLE(_node_id, i2c),                                                         \
		DT_PROP_BY_IDX(_node_id, addrs, _idx));


#define MCTP_I2C_GPIO_CONTROLLER_DEFINE_GPIOS(_node_id, _name)                                     \
	const struct gpio_dt_spec _name##_endpoint_gpios[] = {                                     \
		DT_FOREACH_PROP_ELEM_SEP(_node_id, endpoint_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,))   \
	}

#define MCTP_I2C_GPIO_CONTROLLER_DEFINE_IDS(_node_id, _name)                                       \
	const uint8_t _name##_endpoint_ids[] = DT_PROP(_node_id, endpoint_ids);

#define MCTP_I2C_GPIO_CONTROLLER_DEFINE_GPIO_CBS(_node_id, _name)                                  \
	struct mctp_i2c_gpio_controller_cb \
		_name##_endpoint_gpio_cbs[DT_PROP_LEN(_node_id, endpoint_ids)]


#define MCTP_I2C_GPIO_CONTROLLER_DEFINE_IODEVS(_node_id, _name)                                    \
	DT_FOREACH_PROP_ELEM_VARGS(_node_id, endpoint_addrs,                                       \
			MCTP_I2C_GPIO_CONTROLLER_IODEV_DEFINE, _name)                              \
		const struct rtio_iodev *_name##_endpoint_iodevs[] = {                             \
		LISTIFY(DT_PROP_LEN(_node_id, endpoint_ids), &MCTP_I2C_GPIO_CONTROLLER_IODEV_NAME, \
				(,), _name)                                                        \
	}

/** @endcond INTERNAL_HIDDEN */

/**
 * @brief Define a MCTP bus binding for I2C controller with GPIO
 *
 * Rather than mode switching as the MCTP standard wishes, this is a custom
 * binding. On the controller side each neighbor has an associated i2c address
 * and gpio it will listen for a trigger on. When triggered the controller will
 * read the target device. The target device will be expected to have what amounts
 * to two registers, one containing a message length and another containing a FIFO
 * like register producing the message bytes.
 *
 * Thus the sequence for a I2C target to send a message would be...
 *
 * 1. Setup a length and message value (pseudo registers).
 * 2. Signal the controller with a GPIO level set
 * 3. The controller then is expected to read the length (write of addr 0 + read 1 byte)
 * 4. The controller then is expected to read the message (write of addr 1 + read N bytes)
 * 5. The target clears the pin set
 * 6. The controller reports a message receive to MCTP
 *
 * @param _name Symbolic name of the bus binding variable
 * @param _node_id DeviceTree Node containing the configuration of this MCTP binding
 */
#define MCTP_I2C_GPIO_CONTROLLER_DT_DEFINE(_name, _node_id)                                        \
	RTIO_DEFINE(_name##_rtio_tx, 5, 5);                                                        \
	RTIO_DEFINE(_name##_rtio_rx, 5, 5);                                                        \
	MCTP_I2C_GPIO_CONTROLLER_DEFINE_IODEVS(_node_id, _name);                                   \
	MCTP_I2C_GPIO_CONTROLLER_DEFINE_GPIOS(_node_id, _name);                                    \
	MCTP_I2C_GPIO_CONTROLLER_DEFINE_IDS(_node_id, _name);                                      \
	MCTP_I2C_GPIO_CONTROLLER_DEFINE_GPIO_CBS(_node_id, _name);                                 \
	K_SEM_DEFINE(_name##_tx_lock, 1, 1);                                                       \
	struct mctp_binding_i2c_gpio_controller _name = {                                          \
		.binding = {                                                                       \
			.name = STRINGIFY(_name), .version = 1,                                    \
			.start = mctp_i2c_gpio_controller_start,                                   \
			.tx = mctp_i2c_gpio_controller_tx,                                         \
			.pkt_size = MCTP_I2C_GPIO_MAX_PKT_SIZE,                                    \
		},                                                                                 \
		.i2c = DEVICE_DT_GET(DT_PHANDLE(_node_id, i2c)),                                   \
		.num_endpoints = DT_PROP_LEN(_node_id, endpoint_ids),                              \
		.endpoint_ids = _name##_endpoint_ids,                                              \
		.endpoint_gpios = _name##_endpoint_gpios,                                          \
		.endpoint_gpio_cbs = _name##_endpoint_gpio_cbs,                                    \
		.endpoint_iodevs = _name##_endpoint_iodevs,                                        \
		.r_tx = &_name##_rtio_tx,                                                          \
		.r_rx = &_name##_rtio_rx,                                                          \
		.tx_lock = &_name##_tx_lock,                                                       \
	};

#endif /* ZEPHYR_MCTP_I2C_GPIO_CONTROLLER_H_ */
+1 −0
Original line number Diff line number Diff line
zephyr_library()
zephyr_library_sources(mctp_memory.c)
zephyr_library_sources_ifdef(CONFIG_MCTP_UART mctp_uart.c)
zephyr_library_sources_ifdef(CONFIG_MCTP_I2C_GPIO_CONTROLLER mctp_i2c_gpio_controller.c)
+9 −0
Original line number Diff line number Diff line
@@ -25,6 +25,15 @@ config MCTP_UART
	  Build the MCTP UART binding to use MCTP over Zephyr's async UART
	  interface.

config MCTP_I2C_GPIO_CONTROLLER
	bool "MCTP I2C+GPIO Controller Binding"
	depends on I2C
	depends on I2C_RTIO
	depends on GPIO
	help
	  Build the MCTP I2C+GPIO controller binding to use MCTP over Zephyr's I2C RTIO
	  interface and GPIO interrupts.

module = MCTP
module-str = MCTP
source "subsys/logging/Kconfig.template.log_config"
Loading