pmci: mctp: I2C+GPIO Target binding
Adds a I2C+GPIO Target device binding for MCTP communication over I2C. The binding requires an i2c bus and gpio pin, along with a specified I2C and endpoint address pair. These are then used to create an MCTP binding which can be used to communicate in a peer to peer manner among other MCTP endpoints. Each message transmit signals to the bus controller using a GPIO logical high and is unset on transmission completion. Pending transmitters are queued using a semaphore avoiding memcpy being needed to asynchronously transmit mctp pktbufs. Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
parent
6464329346
commit
cbfe7813c7
28
dts/bindings/pmci/mctp/i2c_gpio_target.yaml
Normal file
28
dts/bindings/pmci/mctp/i2c_gpio_target.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2025 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
A configuration for MCTP bindings to an I2C target with GPIO.
|
||||
|
||||
compatible: "zephyr,mctp-i2c-gpio-target"
|
||||
|
||||
properties:
|
||||
i2c:
|
||||
type: phandle
|
||||
description: |
|
||||
I2C peripheral that is used as an MCTP bus with the target mode API.
|
||||
|
||||
i2c-addr:
|
||||
type: int
|
||||
description: |
|
||||
I2C address of endpoint
|
||||
|
||||
endpoint-id:
|
||||
type: int
|
||||
description: |
|
||||
MCTP endpoint id
|
||||
|
||||
endpoint-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
GPIO connected to the endpoint.
|
||||
91
include/zephyr/pmci/mctp/mctp_i2c_gpio_target.h
Normal file
91
include/zephyr/pmci/mctp/mctp_i2c_gpio_target.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_MCTP_I2C_GPIO_TARGET_H_
|
||||
#define ZEPHYR_MCTP_I2C_GPIO_TARGET_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/pmci/mctp/mctp_i2c_gpio_common.h>
|
||||
#include <libmctp.h>
|
||||
|
||||
/**
|
||||
* @brief An MCTP binding for Zephyr's I2C target interface using GPIO
|
||||
*/
|
||||
struct mctp_binding_i2c_gpio_target {
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
struct mctp_binding binding;
|
||||
const struct device *i2c;
|
||||
struct i2c_target_config i2c_target_cfg;
|
||||
uint8_t endpoint_id;
|
||||
const struct gpio_dt_spec endpoint_gpio;
|
||||
uint8_t reg_addr;
|
||||
bool rxtx;
|
||||
uint8_t rx_idx;
|
||||
struct mctp_pktbuf *rx_pkt;
|
||||
struct k_sem *tx_lock;
|
||||
struct k_sem *tx_complete;
|
||||
uint8_t tx_idx;
|
||||
struct mctp_pktbuf *tx_pkt;
|
||||
/** @endcond INTERNAL_HIDDEN */
|
||||
};
|
||||
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
extern const struct i2c_target_callbacks mctp_i2c_gpio_target_callbacks;
|
||||
int mctp_i2c_gpio_target_start(struct mctp_binding *binding);
|
||||
int mctp_i2c_gpio_target_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt);
|
||||
/** @endcond INTERNAL_HIDDEN */
|
||||
|
||||
/**
|
||||
* @brief Define a MCTP bus binding for I2C target with GPIO
|
||||
*
|
||||
* Rather than mode switching as the MCTP standard wishes, this is a custom
|
||||
* binding. On the target side a gpio pin is set active (could be active low or
|
||||
* active high) when writes are pending. It's expected the controller then
|
||||
* reads from the I2C target device at some point in the future. Reads are
|
||||
* accepted at any time and are expected to contain full mctp packets.
|
||||
*
|
||||
* In effect each device has a pair of pseudo registers for reading or writing
|
||||
* as a packet FIFO.
|
||||
*
|
||||
* 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_TARGET_DT_DEFINE(_name, _node_id) \
|
||||
K_SEM_DEFINE(_name ##_tx_lock, 1, 1); \
|
||||
K_SEM_DEFINE(_name ##_tx_complete, 0, 1); \
|
||||
struct mctp_binding_i2c_gpio_target _name = { \
|
||||
.binding = { \
|
||||
.name = STRINGIFY(_name), .version = 1, \
|
||||
.start = mctp_i2c_gpio_target_start, \
|
||||
.tx = mctp_i2c_gpio_target_tx, \
|
||||
.pkt_size = MCTP_I2C_GPIO_MAX_PKT_SIZE, \
|
||||
}, \
|
||||
.i2c = DEVICE_DT_GET(DT_PHANDLE(_node_id, i2c)), \
|
||||
.i2c_target_cfg = { \
|
||||
.address = DT_PROP(_node_id, i2c_addr), \
|
||||
.callbacks = &mctp_i2c_gpio_target_callbacks, \
|
||||
}, \
|
||||
.endpoint_id = DT_PROP(_node_id, endpoint_id), \
|
||||
.endpoint_gpio = GPIO_DT_SPEC_GET(_node_id, endpoint_gpios), \
|
||||
.tx_lock = &_name##_tx_lock, \
|
||||
.tx_complete = &_name##_tx_complete, \
|
||||
};
|
||||
|
||||
#endif /* ZEPHYR_MCTP_I2C_GPIO_TARGET_H_ */
|
||||
@ -2,3 +2,4 @@ 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)
|
||||
zephyr_library_sources_ifdef(CONFIG_MCTP_I2C_GPIO_TARGET mctp_i2c_gpio_target.c)
|
||||
|
||||
@ -32,7 +32,16 @@ config MCTP_I2C_GPIO_CONTROLLER
|
||||
depends on GPIO
|
||||
help
|
||||
Build the MCTP I2C+GPIO controller binding to use MCTP over Zephyr's I2C RTIO
|
||||
interface and GPIO interrupts.
|
||||
interface and GPIO interrupts from targets to signal their request to write.
|
||||
|
||||
config MCTP_I2C_GPIO_TARGET
|
||||
bool "MCTP I2C+GPIO Target Binding"
|
||||
depends on I2C
|
||||
depends on GPIO
|
||||
help
|
||||
Build the MCTP I2C+GPIO target binding to use MCTP over Zephyr's I2C target
|
||||
interface and GPIO to signal writes to the bus controller.
|
||||
|
||||
|
||||
module = MCTP
|
||||
module-str = MCTP
|
||||
|
||||
225
subsys/pmci/mctp/mctp_i2c_gpio_target.c
Normal file
225
subsys/pmci/mctp/mctp_i2c_gpio_target.c
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include "zephyr/pmci/mctp/mctp_i2c_gpio_common.h"
|
||||
#include <zephyr/sys/__assert.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/pmci/mctp/mctp_i2c_gpio_target.h>
|
||||
#include <crc-16-ccitt.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(mctp_i2c_gpio_target, CONFIG_MCTP_LOG_LEVEL);
|
||||
|
||||
int mctp_i2c_gpio_target_write_requested(struct i2c_target_config *config)
|
||||
{
|
||||
|
||||
struct mctp_binding_i2c_gpio_target *b =
|
||||
CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
|
||||
|
||||
if (b->rxtx || b->reg_addr == MCTP_I2C_GPIO_INVALID_ADDR) {
|
||||
/* Reset our state */
|
||||
b->reg_addr = MCTP_I2C_GPIO_INVALID_ADDR;
|
||||
b->rxtx = false;
|
||||
b->rx_idx = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mctp_i2c_gpio_target_write_received(struct i2c_target_config *config, uint8_t val)
|
||||
{
|
||||
struct mctp_binding_i2c_gpio_target *b =
|
||||
CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
|
||||
int ret = 0;
|
||||
|
||||
switch (b->reg_addr) {
|
||||
case MCTP_I2C_GPIO_INVALID_ADDR:
|
||||
b->rxtx = false;
|
||||
b->reg_addr = val;
|
||||
break;
|
||||
case MCTP_I2C_GPIO_RX_MSG_LEN_ADDR:
|
||||
b->rxtx = true;
|
||||
b->rx_pkt = mctp_pktbuf_alloc(&b->binding, (size_t)val);
|
||||
break;
|
||||
case MCTP_I2C_GPIO_RX_MSG_ADDR:
|
||||
b->rxtx = true;
|
||||
b->rx_pkt->data[b->rx_idx] = val;
|
||||
b->rx_idx += 1;
|
||||
|
||||
/* buffer full */
|
||||
if (b->rx_idx >= b->rx_pkt->size) {
|
||||
ret = -ENOMEM;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Write when reg_addr is %d", b->reg_addr);
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mctp_i2c_gpio_target_read_requested(struct i2c_target_config *config, uint8_t *val)
|
||||
{
|
||||
struct mctp_binding_i2c_gpio_target *b =
|
||||
CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
|
||||
uint8_t pkt_len;
|
||||
int ret = 0;
|
||||
|
||||
switch (b->reg_addr) {
|
||||
case MCTP_I2C_GPIO_TX_MSG_LEN_ADDR:
|
||||
b->rxtx = true;
|
||||
if (b->tx_pkt == NULL) {
|
||||
LOG_WRN("empty packet?");
|
||||
pkt_len = 0;
|
||||
} else {
|
||||
pkt_len = (uint8_t)(b->tx_pkt->end - b->tx_pkt->start);
|
||||
}
|
||||
*val = pkt_len;
|
||||
break;
|
||||
case MCTP_I2C_GPIO_TX_MSG_ADDR:
|
||||
b->rxtx = true;
|
||||
*val = b->tx_pkt->data[b->tx_pkt->start];
|
||||
b->tx_idx = b->tx_pkt->start;
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("invalid rre reg %d", b->reg_addr);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mctp_i2c_gpio_target_read_processed(struct i2c_target_config *config, uint8_t *val)
|
||||
{
|
||||
struct mctp_binding_i2c_gpio_target *b =
|
||||
CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
|
||||
|
||||
b->tx_idx += 1;
|
||||
|
||||
if (b->reg_addr != MCTP_I2C_GPIO_TX_MSG_ADDR) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (b->tx_idx > b->tx_pkt->end) {
|
||||
LOG_WRN("rrp past end reg %d", b->reg_addr);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
*val = b->tx_pkt->data[b->tx_idx];
|
||||
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mctp_i2c_gpio_target_stop(struct i2c_target_config *config)
|
||||
{
|
||||
struct mctp_binding_i2c_gpio_target *b =
|
||||
CONTAINER_OF(config, struct mctp_binding_i2c_gpio_target, i2c_target_cfg);
|
||||
uint8_t pkt_len;
|
||||
|
||||
if (b->rxtx) {
|
||||
switch (b->reg_addr) {
|
||||
case MCTP_I2C_GPIO_TX_MSG_ADDR:
|
||||
pkt_len = (b->tx_pkt->end - b->tx_pkt->start);
|
||||
if (b->tx_idx < pkt_len - 1) {
|
||||
LOG_WRN("Only %d of %d bytes of the transmit packet were read",
|
||||
b->tx_idx, pkt_len);
|
||||
}
|
||||
b->tx_pkt = NULL;
|
||||
k_sem_give(b->tx_complete);
|
||||
break;
|
||||
|
||||
case MCTP_I2C_GPIO_RX_MSG_ADDR:
|
||||
LOG_DBG("stop rx msg, give pkt");
|
||||
/* Give message to mctp to process */
|
||||
mctp_bus_rx(&b->binding, b->rx_pkt);
|
||||
b->rx_pkt = NULL;
|
||||
break;
|
||||
case MCTP_I2C_GPIO_RX_MSG_LEN_ADDR:
|
||||
case MCTP_I2C_GPIO_TX_MSG_LEN_ADDR:
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("unexpected stop for reg %d", b->reg_addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct i2c_target_callbacks mctp_i2c_gpio_target_callbacks = {
|
||||
.write_requested = mctp_i2c_gpio_target_write_requested,
|
||||
.read_requested = mctp_i2c_gpio_target_read_requested,
|
||||
.write_received = mctp_i2c_gpio_target_write_received,
|
||||
.read_processed = mctp_i2c_gpio_target_read_processed,
|
||||
.stop = mctp_i2c_gpio_target_stop,
|
||||
};
|
||||
|
||||
/*
|
||||
* libmctp wants us to return once the packet is sent not before
|
||||
* so the entire process of flagging the tx with gpio, waiting on the read,
|
||||
* needs to complete before we can move on.
|
||||
*
|
||||
* this is called for each packet in the packet queue libmctp provides
|
||||
*/
|
||||
int mctp_i2c_gpio_target_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt)
|
||||
{
|
||||
struct mctp_binding_i2c_gpio_target *b =
|
||||
CONTAINER_OF(binding, struct mctp_binding_i2c_gpio_target, binding);
|
||||
int rc;
|
||||
|
||||
k_sem_take(b->tx_lock, K_FOREVER);
|
||||
|
||||
b->tx_pkt = pkt;
|
||||
|
||||
rc = gpio_pin_set_dt(&b->endpoint_gpio, 1);
|
||||
if (rc != 0) {
|
||||
LOG_ERR("failed to set gpio pin");
|
||||
b->tx_pkt = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
k_sem_take(b->tx_complete, K_FOREVER);
|
||||
|
||||
rc = gpio_pin_set_dt(&b->endpoint_gpio, 0);
|
||||
if (rc != 0) {
|
||||
LOG_ERR("failed to clear gpio pin");
|
||||
}
|
||||
|
||||
out:
|
||||
k_sem_give(b->tx_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int mctp_i2c_gpio_target_start(struct mctp_binding *binding)
|
||||
{
|
||||
struct mctp_binding_i2c_gpio_target *b =
|
||||
CONTAINER_OF(binding, struct mctp_binding_i2c_gpio_target, binding);
|
||||
int rc;
|
||||
|
||||
/* Register i2c target */
|
||||
rc = i2c_target_register(b->i2c, &b->i2c_target_cfg);
|
||||
if (rc != 0) {
|
||||
LOG_ERR("failed to register i2c target");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Configure pin to use as data ready signaling */
|
||||
rc = gpio_pin_configure_dt(&b->endpoint_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
if (rc != 0) {
|
||||
LOG_ERR("failed to configure gpio");
|
||||
goto out;
|
||||
}
|
||||
|
||||
mctp_binding_set_tx_enabled(binding, true);
|
||||
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user