diff --git a/hw/ast-bmc/Makefile.inc b/hw/ast-bmc/Makefile.inc
index e7ded0e8..ca7de379 100644
--- a/hw/ast-bmc/Makefile.inc
+++ b/hw/ast-bmc/Makefile.inc
@@ -2,5 +2,12 @@
 SUBDIRS += hw/ast-bmc
 
 AST_BMC_OBJS  = ast-io.o ast-sf-ctrl.o
+
+ifeq ($(CONFIG_PLDM),1)
+CPPFLAGS += -I$(SRC)/libmctp/
+#CFLAGS += -DASTLPC_DEBUG
+AST_BMC_OBJS += ast-mctp.o
+endif
+
 AST_BMC = hw/ast-bmc/built-in.a
 $(AST_BMC): $(AST_BMC_OBJS:%=hw/ast-bmc/%)
diff --git a/hw/ast-bmc/ast-mctp.c b/hw/ast-bmc/ast-mctp.c
new file mode 100644
index 00000000..9274d77f
--- /dev/null
+++ b/hw/ast-bmc/ast-mctp.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+// Copyright 2022 IBM Corp.
+
+#define pr_fmt(fmt) "AST-MCTP: " fmt
+
+#include <lpc.h>
+#include <interrupts.h>
+#include <timer.h>
+#include <timebase.h>
+#include <debug_descriptor.h>
+#include <ast.h>
+#include <console.h>
+#include <libmctp.h>
+#include <libmctp-cmds.h>
+#include <libmctp-log.h>
+#include <libmctp-astlpc.h>
+
+/*
+ * For the EIDs: the valid range is 8-254.
+ * host default = 9
+ */
+uint8_t HOST_EID = 9;
+
+static struct mctp *mctp;
+
+/* binding specific */
+struct astlpc_ops_data {
+
+	uint16_t kcs_data_addr; /* LPC IO space offset for the data register */
+	uint16_t kcs_stat_addr;
+
+	/* address of the packet exchange buffer in FW space */
+	uint32_t lpc_fw_addr;
+};
+
+/*
+ * The AST binding is described here:
+ *
+ * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-astlpc.md
+ *
+ * Most of the binding is implemented in libmctp, but we need to provide
+ * accessors for the LPC FW space (for the packet buffer) and for the KCS
+ * peripheral (for the interrupt mechanism).
+ */
+
+static int ast_kcs_reg_read(void *binding_data,
+			    enum mctp_binding_astlpc_kcs_reg reg,
+			    uint8_t *val)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+	uint32_t addr, tmp;
+	int rc;
+
+	if (reg == MCTP_ASTLPC_KCS_REG_STATUS)
+		addr = ops_data->kcs_stat_addr;
+	else if (reg == MCTP_ASTLPC_KCS_REG_DATA)
+		addr = ops_data->kcs_data_addr;
+	else
+		return OPAL_PARAMETER;
+
+	rc = lpc_read(OPAL_LPC_IO, addr, &tmp, 1);
+	if (!rc)
+		*val = tmp & 0xff;
+	else
+		*val = 0xff;
+
+	mctp_prdebug("%s 0x%hhx from %s (%d)",
+		     __func__, *val, reg ? "status" : "data", rc);
+
+	return rc;
+}
+
+static int ast_kcs_reg_write(void *binding_data,
+			     enum mctp_binding_astlpc_kcs_reg reg,
+			     uint8_t val)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+	uint32_t addr;
+
+	mctp_prdebug("%s 0x%hhx to %s",
+		     __func__, val, reg ? "status" : "data");
+
+	if (reg == MCTP_ASTLPC_KCS_REG_STATUS)
+		addr = ops_data->kcs_stat_addr;
+	else if (reg == MCTP_ASTLPC_KCS_REG_DATA)
+		addr = ops_data->kcs_data_addr;
+	else
+		return OPAL_PARAMETER;
+
+	return lpc_write(OPAL_LPC_IO, addr, val, 1);
+}
+
+static int ast_lpc_read(void *binding_data, void *buf, long offset,
+			size_t len)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+
+	mctp_prdebug("%s %zu bytes from 0x%lx (lpc: 0x%lx)",
+		     __func__, len, offset,
+		     ops_data->lpc_fw_addr + offset);
+	return lpc_fw_read(ops_data->lpc_fw_addr + offset, buf, len);
+}
+
+static int ast_lpc_write(void *binding_data, const void *buf,
+			 long offset, size_t len)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+
+	mctp_prdebug("%s %zu bytes to offset 0x%lx (lpc: 0x%lx)",
+		     __func__, len, offset,
+		     ops_data->lpc_fw_addr + offset);
+	return lpc_fw_write(ops_data->lpc_fw_addr + offset, buf, len);
+}
+
+static const struct mctp_binding_astlpc_ops astlpc_ops = {
+	.kcs_read = ast_kcs_reg_read,
+	.kcs_write = ast_kcs_reg_write,
+	.lpc_read = ast_lpc_read,
+	.lpc_write = ast_lpc_write,
+};
+
+static struct mctp_binding_astlpc *ast_binding;
+static struct astlpc_ops_data *ops_data;
+
+/* Keyboard Controller Style (KCS) data register address */
+#define KCS_DATA_REG 0xca2
+
+/* Keyboard Controller Style (KCS) status register address */
+#define KCS_STATUS_REG 0xca3
+
+/* Serial IRQ number for KCS interface */
+#define KCS_SERIAL_IRQ 11
+
+bool irq_active;
+struct timer mctp_boot_timer;
+
+/* we need a poller to crank the mctp state machine during boot */
+static void ast_mctp_poller(struct timer *t __unused,
+			    void *data __unused, uint64_t now __unused)
+{
+	mctp_astlpc_poll(ast_binding);
+
+	/* once booted we can rely on the KCS interrupt */
+	if ((opal_booting() && !irq_active))
+		schedule_timer(&mctp_boot_timer, msecs_to_tb(5));
+}
+
+/* at runtime the interrupt should handle it */
+static void ast_mctp_interrupt(uint32_t chip_id __unused,
+			       uint32_t irq_msk __unused)
+{
+	if (!irq_active) {
+		mctp_prerr("IRQ Active! Disabling boot poller...");
+		irq_active = true;
+	}
+
+	mctp_astlpc_poll(ast_binding);
+}
+
+static struct lpc_client kcs_lpc_client = {
+	.reset = NULL,
+	.interrupt = ast_mctp_interrupt,
+	.interrupts = LPC_IRQ(KCS_SERIAL_IRQ),
+};
+
+static bool ast_mctp_supported(void)
+{
+	uint32_t tmp;
+
+	/*
+	 * Probe for MCTP support by reading the STAT register of KCS#4
+	 */
+	tmp = 0;
+	return true;
+
+	if (lpc_probe_read(OPAL_LPC_IO, KCS_STATUS_REG, &tmp, 1)) {
+		mctp_prdebug("KCS4 probe failed, skipping MCTP init");
+		return false;
+	}
+
+	return !!(tmp & 0x80); /* high bit indicates BMC is listening */
+}
+
+#define DESIRED_MTU 32768
+
+static struct mctp_binding_astlpc *ast_mctp_binding(void)
+{
+	struct mctp_binding_astlpc *ast_binding;
+
+	ops_data = zalloc(sizeof(struct astlpc_ops_data));
+	if (!ops_data)
+		return NULL;
+
+	/*
+	 * Current OpenBMC systems put the MCTP buffer 1MB down from
+	 * the end of the LPC FW range.
+	 *
+	 * The size of the FW range is: 0x1000_0000 so the window be at:
+	 *
+	 *   0x1000_0000 - 2**20 == 0xff00000
+	 */
+	ops_data->lpc_fw_addr = 0xff00000;
+
+	/* values chosen by the OpenBMC driver */
+	ops_data->kcs_data_addr = KCS_DATA_REG;
+	ops_data->kcs_stat_addr = KCS_STATUS_REG;
+
+	/* Initialise the binding */
+	ast_binding = mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_HOST,
+				       DESIRED_MTU,
+				       NULL,
+				       &astlpc_ops,
+				       ops_data);
+	if (!ast_binding) {
+		mctp_prerr("binding init failed");
+		goto err;
+	}
+
+	/* register an lpc client so we get an interrupt */
+	lpc_register_client(0, &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL);
+
+	init_timer(&mctp_boot_timer, ast_mctp_poller, ast_binding);
+	schedule_timer(&mctp_boot_timer, msecs_to_tb(5));
+
+	return ast_binding;
+
+err:
+	mctp_astlpc_destroy(ast_binding);
+	free(ops_data);
+	return NULL;
+}
+
+static void *mctp_malloc(size_t size) { return malloc(size); }
+static void mctp_free(void *ptr) { return free(ptr); }
+static void *mctp_realloc(void *ptr, size_t size)
+{
+	return realloc(ptr, size);
+}
+
+#ifdef ASTLPC_DEBUG
+char buffer[320];
+static void mctp_log(int log_lvl, const char *fmt, va_list va)
+{
+	snprintf(buffer, sizeof(buffer), "%s\n", fmt);
+	vprlog(log_lvl, buffer, va);
+}
+#endif
+
+int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len)
+{
+	uint8_t tag = 0;
+
+	return mctp_message_tx(mctp, eid, MCTP_MESSAGE_TO_DST, tag, msg, len);
+}
+
+void ast_mctp_stop_polling(void)
+{
+	irq_active = true;
+}
+
+#define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072
+
+/*
+ * Initialize mctp binding for hbrt and provide interfaces for sending
+ * and receiving mctp messages.
+ */
+int ast_mctp_init(void (*fn)(uint8_t src_eid, bool tag_owner,
+			     uint8_t msg_tag, void *data, void *msg,
+			     size_t len))
+{
+	uint64_t start;
+
+	if (!ast_mctp_supported())
+		return OPAL_HARDWARE;
+
+	/* skiboot's malloc/free/realloc are macros so they need
+	 * wrappers
+	 */
+	mctp_set_alloc_ops(mctp_malloc, mctp_free, mctp_realloc);
+
+	/*
+	 * /-----\                                 /---------\
+	 * | bmc | (eid: 8) <- lpc/kcs -> (eid: 9) | skiboot |
+	 * \-----/                                 \---------/
+	 *                astlpc kcs binding
+	 */
+	mctp = mctp_init();
+	if (!mctp) {
+		mctp_prerr("mctp init failed");
+		return OPAL_HARDWARE;
+	}
+
+#ifdef ASTLPC_DEBUG
+	/* Setup the trace hook */
+	mctp_set_log_custom(mctp_log);
+#endif
+
+	mctp_prinfo("libmctp initialized");
+
+	/* Set the max message size to be large enough */
+	mctp_set_max_message_size(mctp, HOST_MAX_INCOMING_MESSAGE_ALLOCATION);
+
+	/* Setup the message rx callback */
+	mctp_set_rx_all(mctp, fn, NULL);
+
+	/* Initialise the binding */
+	ast_binding = ast_mctp_binding();
+	if (!ast_binding)
+		goto err;
+
+	/* Register the binding to the LPC bus we are using for this
+	 * MCTP configuration.
+	 */
+	if (mctp_register_bus(mctp,
+			      mctp_binding_astlpc_core(ast_binding),
+			      HOST_EID)) {
+		mctp_prerr("failed to register bus");
+		goto err;
+	}
+
+	/* Initializing the channel requires running the poll function
+	 * a few times to handle the initial setup since we need the
+	 * BMC to reply.
+	 *
+	 * Unfortuntately there's no good way to determine if this
+	 * worked or not so poll for a bit with a timeout. :(
+	 *
+	 * We have to check (bus->state == mctp_bus_state_constructed)
+	 * otherwise mctp_message_tx() will reject the message.
+	 * astlpc: host: Version negotiation got: 3
+	 */
+	start = mftb();
+	while (tb_compare(mftb(), start + msecs_to_tb(250)) == TB_ABEFOREB)
+		time_wait_ms(10);
+
+	mctp_prwarn("%s - The mctp bus state should be correct now !!!",
+		    __func__);
+
+	return OPAL_SUCCESS;
+
+err:
+	mctp_prerr("Unable to initialize MCTP");
+	mctp_destroy(mctp);
+	mctp = NULL;
+
+	return OPAL_HARDWARE;
+}
diff --git a/include/ast.h b/include/ast.h
index 5e932398..e21774be 100644
--- a/include/ast.h
+++ b/include/ast.h
@@ -91,6 +91,13 @@ void ast_setup_ibt(uint16_t io_base, uint8_t irq);
 /* MBOX configuration */
 void ast_setup_sio_mbox(uint16_t io_base, uint8_t irq);
 
+/* AST MCTP configuration */
+int ast_mctp_init(void (*fn)(uint8_t src_eid, bool tag_owner,
+			     uint8_t msg_tag, void *data, void *msg,
+			     size_t len));
+void ast_mctp_stop_polling(void);
+int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len);
+
 #endif /* __SKIBOOT__ */
 
 /*
