diff mbox series

[v2,3/4] libpdbg: Add i2c get and put functions for i2c master on CFAM

Message ID 20190418012658.23315-4-rashmica.g@gmail.com
State Accepted
Headers show
Series Add i2c put and get to pdbg | expand

Checks

Context Check Description
snowpatch_ozlabs/apply_patch success Successfully applied on branch master (854c4c5facff43af9e0fe5d7062b58f631987b0b)
snowpatch_ozlabs/build-multiarch fail Test build-multiarch on branch master

Commit Message

Rashmica Gupta April 18, 2019, 1:26 a.m. UTC
This enables the two basic i2c functions from the BMC.

Signed-off-by: Rashmica Gupta <rashmica.g@gmail.com>
---
 libpdbg/i2cm.c    | 428 ++++++++++++++++++++++++++++++++++++++++++++++
 libpdbg/libpdbg.h |   2 +
 libpdbg/target.c  |  11 ++
 libpdbg/target.h  |   5 +
 p9-kernel.dts.m4  |   2 +-
 src/i2c.c         |  26 +++
 src/main.c        |   5 +-
 7 files changed, 476 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/libpdbg/i2cm.c b/libpdbg/i2cm.c
index 3e0218d..c260405 100644
--- a/libpdbg/i2cm.c
+++ b/libpdbg/i2cm.c
@@ -31,6 +31,434 @@ 
 #include <sys/param.h>
 #include <dirent.h>
 
+/* I2C common registers */
+#define I2C_FIFO_REG 		0x0
+#define I2C_CMD_REG 		0x1
+#define I2C_MODE_REG 		0x2
+#define I2C_WATERMARK_REG	0x3
+#define I2C_INT_MASK_REG	0x4
+#define I2C_INT_COND_REG	0x5
+#define I2C_STATUS_REG		0x7
+#define I2C_IMD_RESET_REG	0x7
+#define I2C_IMD_RESET_ERR_REG	0x8
+#define I2C_ESTAT_REG		0x8
+#define I2C_RESIDUAL_REG	0x9
+#define I2C_PORT_BUSY_REG	0xA
+
+#define I2C_PIB_OFFSET 		0x4
+#define I2C_PIB_ENGINE_0 	0x0000
+#define I2C_PIB_ENGINE_1 	0x1000
+#define I2C_PIB_ENGINE_2 	0x2000
+#define I2C_PIB_ENGINE_3 	0x3000
+
+/* I2C command register bits */
+#define I2C_CMD_WITH_START		PPC_BIT32(0)
+#define I2C_CMD_WITH_ADDR		PPC_BIT32(1)
+#define I2C_CMD_READ_CONTINUE	PPC_BIT32(2)
+#define I2C_CMD_WITH_STOP		PPC_BIT32(3)
+#define I2C_CMD_INT_STEER		PPC_BITMASK32(6, 7)
+#define I2C_CMD_DEV_ADDR		PPC_BITMASK32(8, 14)
+#define I2C_CMD_READ_NOT_WRITE	PPC_BIT32(15)
+#define I2C_CMD_LENGTH			PPC_BITMASK32(16, 31)
+
+/* I2C mode register bits */
+#define I2C_MODE_BIT_RATE_DIV	PPC_BITMASK32(0, 15)
+#define I2C_MODE_PORT_NUM		PPC_BITMASK32(16, 21)
+#define I2C_ENHANCED_MODE		PPC_BIT32(28)
+#define I2C_MODE_PACING			PPC_BIT32(30)
+
+/* watermark */
+#define I2C_WATERMARK_HIGH		PPC_BITMASK32(16,19)
+#define I2C_WATERMARK_LOW		PPC_BITMASK32(24,27)
+
+/* I2C status register */
+#define I2C_STAT_INV_CMD		PPC_BIT32(0)
+#define I2C_STAT_PARITY			PPC_BIT32(1)
+#define I2C_STAT_BE_OVERRUN		PPC_BIT32(2)
+#define I2C_STAT_BE_ACCESS		PPC_BIT32(3)
+#define I2C_STAT_LOST_ARB		PPC_BIT32(4)
+#define I2C_STAT_NACK			PPC_BIT32(5)
+#define I2C_STAT_DAT_REQ		PPC_BIT32(6)
+#define I2C_STAT_CMD_COMP		PPC_BIT32(7)
+#define I2C_STAT_STOP_ERR		PPC_BIT32(8)
+#define I2C_STAT_MAX_PORT		PPC_BITMASK32(9, 15)
+#define I2C_STAT_ANY_INT		PPC_BIT32(16)
+#define I2C_STAT_WAIT_BUSY		PPC_BIT32(17)
+#define I2C_STAT_ERR_IN			PPC_BIT32(18)
+#define I2C_STAT_PORT_HIST_BUSY	PPC_BIT32(19)
+#define I2C_STAT_SCL_IN			PPC_BIT32(20)
+#define I2C_STAT_SDA_IN			PPC_BIT32(21)
+#define I2C_STAT_PORT_BUSY		PPC_BIT32(22)
+#define I2C_STAT_SELF_BUSY		PPC_BIT32(23)
+#define I2C_STAT_FIFO_COUNT		PPC_BITMASK32(24, 31)
+
+#define I2C_STAT_ERR		(I2C_STAT_INV_CMD | I2C_STAT_PARITY	\
+							 | I2C_STAT_BE_OVERRUN | I2C_STAT_BE_ACCESS \
+							 | I2C_STAT_LOST_ARB | I2C_STAT_NACK \
+							 | I2C_STAT_STOP_ERR)
+
+/* I2C extended status register */
+#define I2C_ESTAT_FIFO_SIZE PPC_BITMASK32(0,7)
+#define I2C_ESTAT_MSM_STATE PPC_BITMASK32(11,15)
+#define I2C_ESTAT_HIGH_WATER 	PPC_BIT32(22)
+#define I2C_ESTAT_LOW_WATER 	PPC_BIT32(23)
+
+/* I2C interrupt mask register */
+#define I2C_INT_INV_CMD		PPC_BIT32(16)
+#define I2C_INT_PARITY		PPC_BIT32(17)
+#define I2C_INT_BE_OVERRUN	PPC_BIT32(18)
+#define I2C_INT_BE_ACCESS	PPC_BIT32(19)
+#define I2C_INT_LOST_ARB	PPC_BIT32(20)
+#define I2C_INT_NACK		PPC_BIT32(21)
+#define I2C_INT_DAT_REQ		PPC_BIT32(22)
+#define I2C_INT_CMD_COMP	PPC_BIT32(23)
+#define I2C_INT_STOP_ERR	PPC_BIT32(24)
+#define I2C_INT_BUSY		PPC_BIT32(25)
+#define I2C_INT_IDLE		PPC_BIT32(26)
+
+/* I2C residual  register */
+#define I2C_RESID_FRONT		PPC_BITMASK32(0,15)
+#define I2C_RESID_BACK		PPC_BITMASK32(16,31)
+
+static int _i2cm_reg_write(struct i2cm *i2cm, uint32_t addr, uint32_t data)
+{
+	CHECK_ERR(fsi_write(&i2cm->target, addr, data));
+	return 0;
+}
+
+static int _i2cm_reg_read(struct i2cm *i2cm, uint32_t addr, uint32_t *data)
+{
+	CHECK_ERR(fsi_read(&i2cm->target, addr, data));
+	return 0;
+}
+
+static void debug_print_reg(struct i2cm *i2cm)
+{
+	uint32_t fsidata = 0;
+
+	PR_INFO("\t --------\n");
+	_i2cm_reg_read(i2cm,  I2C_STATUS_REG, &fsidata);
+	PR_INFO("\t status reg \t has value 0x%x \n", fsidata);
+	if (fsidata & I2C_STAT_INV_CMD)
+		 PR_INFO("\t\tinvalid cmd\n");
+	if (fsidata & I2C_STAT_PARITY)
+		 PR_INFO("\t\tparity\n");
+	if (fsidata & I2C_STAT_BE_OVERRUN)
+		 PR_INFO("\t\tback endoverrun\n");
+	if (fsidata & I2C_STAT_BE_ACCESS)
+		 PR_INFO("\t\tback end access error\n");
+	if (fsidata & I2C_STAT_LOST_ARB)
+		 PR_INFO("\t\tarbitration lost\n");
+	if (fsidata & I2C_STAT_NACK)
+		 PR_INFO("\t\tnack\n");
+	if (fsidata & I2C_STAT_DAT_REQ)
+		 PR_INFO("\t\tdata request\n");
+	if (fsidata & I2C_STAT_STOP_ERR)
+		 PR_INFO("\t\tstop error\n");
+	if (fsidata & I2C_STAT_PORT_BUSY)
+		 PR_INFO("\t\ti2c busy\n");
+	PR_INFO("\t\tfifo entry count: %li \n",fsidata&I2C_STAT_FIFO_COUNT);
+
+	_i2cm_reg_read(i2cm,  I2C_ESTAT_REG, &fsidata);
+	PR_INFO("\t extended status reg has value 0x%x \n", fsidata);
+	if (fsidata & I2C_ESTAT_HIGH_WATER)
+		PR_INFO("\t\thigh water mark reached\n");
+	if (fsidata & I2C_ESTAT_LOW_WATER)
+		PR_INFO("\t\tlow water mark reached\n");
+
+
+	_i2cm_reg_read(i2cm,  I2C_WATERMARK_REG, &fsidata);
+	PR_INFO("\t watermark reg  has value 0x%x \n", fsidata);
+	PR_INFO("\t\twatermark high: %li \n",fsidata&I2C_WATERMARK_HIGH);
+	PR_INFO("\t\twatermark low: %li \n",fsidata&I2C_WATERMARK_LOW);
+
+	_i2cm_reg_read(i2cm,  I2C_RESIDUAL_REG, &fsidata);
+	PR_INFO("\t residual reg  has value 0x%x \n", fsidata);
+	PR_INFO("\t\tfrontend_len: %li \n",fsidata&I2C_RESID_FRONT);
+	PR_INFO("\t\tbackend_len: %li \n",fsidata&I2C_RESID_BACK);
+
+	_i2cm_reg_read(i2cm,  I2C_PORT_BUSY_REG, &fsidata);
+	PR_INFO("\t port busy reg  has value 0x%x \n", fsidata);
+	PR_INFO("\t -------\n");
+
+}
+
+static void i2c_mode_write(struct i2cm *i2cm, uint16_t port)
+{
+	uint32_t data = I2C_MODE_PACING;
+
+	// hardcoding bit rate divisor because not too important
+	data = SETFIELD(I2C_MODE_BIT_RATE_DIV, data, 28);
+	data = SETFIELD(I2C_MODE_PORT_NUM, data, port);
+	PR_INFO("setting mode to %x\n", data);
+	_i2cm_reg_write(i2cm, I2C_MODE_REG, data);
+}
+
+static void i2c_watermark_write(struct i2cm *i2cm)
+{
+	uint32_t data = 0;
+
+	data = SETFIELD(I2C_WATERMARK_HIGH, data, 4);
+	data = SETFIELD(I2C_WATERMARK_LOW, data, 4);
+	PR_INFO("setting watermark (0x%x) to: %x\n", I2C_WATERMARK_REG, data);
+	_i2cm_reg_write(i2cm, I2C_WATERMARK_REG, data);
+}
+
+static int i2c_reset(struct i2cm *i2cm)
+{
+	uint32_t fsidata = 0;
+	debug_print_reg(i2cm);
+	PR_INFO("### RESETING i2cm \n");
+
+	fsidata = 0xB;
+	_i2cm_reg_write(i2cm, I2C_IMD_RESET_REG, fsidata);
+	_i2cm_reg_write(i2cm, I2C_IMD_RESET_ERR_REG, fsidata);
+
+	usleep(10000);
+	debug_print_reg(i2cm);
+	return 0;
+}
+
+/*
+ *	If there are errors in the status register, redo the whole
+ *	operation after resetting the i2cm.
+*/
+static int i2c_poll_status(struct i2cm *i2cm, uint32_t *data)
+{
+	int loop;
+
+	for (loop = 0; loop < 10; loop++)
+	{
+		usleep(10000);
+		_i2cm_reg_read(i2cm, I2C_STATUS_REG, data);
+
+		if (((*data) & I2C_STAT_CMD_COMP))
+			break;
+	}
+	if ((*data) & I2C_STAT_PORT_BUSY)
+		PR_INFO("portbusy\n");
+	if ((*data) & I2C_STAT_ERR) {
+		PR_INFO("ERROR IN STATUS\n");
+		debug_print_reg(i2cm);
+		return 1;
+	}
+	return 0;
+}
+
+static int i2c_fifo_write(struct i2cm *i2cm, uint32_t *data, uint16_t size)
+{
+	int bytes_in_fifo = 1;
+	int rc = 0, bytes_written = 0;
+	uint32_t status;
+
+	while (bytes_written < size) {
+
+		if (bytes_written == size)
+			return 0;
+
+		/* Poll status register's FIFO_ENTRY_COUNT to check that FIFO isn't full */
+		rc = i2c_poll_status(i2cm, &status);
+		bytes_in_fifo = status & I2C_STAT_FIFO_COUNT;
+		PR_INFO("%x bytes in fifo \n", bytes_in_fifo);
+
+		if (rc)
+			return 0;
+
+		if (bytes_in_fifo == 8)
+			continue;
+
+		PR_INFO("\twriting: %x  to FIFO\n", data[bytes_written / 4]);
+		rc = _i2cm_reg_write(i2cm, I2C_FIFO_REG, data[bytes_written / 4]);
+		if (rc)
+			return bytes_written;
+		bytes_written += 4;
+	}
+	return bytes_written;
+}
+
+static int i2c_fifo_read(struct i2cm *i2cm, uint32_t *data, uint16_t size)
+{
+	int bytes_to_read = 1;
+	int rc = 0, bytes_read = 0;
+	uint32_t tmp;
+	uint32_t status;
+
+	while (bytes_to_read) {	
+
+		if (bytes_read > size)
+			return 0;
+
+		/* Poll status register's FIFO_ENTRY_COUNT to check that there is data to consume */
+		rc = i2c_poll_status(i2cm, &status);
+		bytes_to_read = status & I2C_STAT_FIFO_COUNT;
+		PR_INFO("%x bytes in fifo \n", bytes_to_read);
+
+		if (rc)
+			return 0;
+
+		if (!bytes_to_read)
+			continue;
+
+		rc = _i2cm_reg_read(i2cm, I2C_FIFO_REG, &tmp);
+		if (rc)
+			return bytes_read;
+		memcpy(data + (bytes_read / 4), &tmp, 4);
+		PR_INFO(" %x \n", data[bytes_read / 4]);
+		bytes_read += 4;
+	}
+	return bytes_read;
+}
+
+static int i2cm_ok_to_use(struct i2cm *i2cm, uint16_t port)
+{
+	uint32_t data;
+
+	_i2cm_reg_read(i2cm, I2C_STATUS_REG, &data);
+
+	if (!(data & I2C_STAT_CMD_COMP) || (data & I2C_STAT_ERR))
+	{
+		PR_INFO("Attempting to reset the i2cm %x \n", data);
+		i2c_reset(i2cm);
+		_i2cm_reg_read(i2cm, I2C_STATUS_REG, &data);
+	}
+	if (data & I2C_STAT_ERR) {
+		PR_ERROR("I2C master error:  %x\n", data);
+		return 0;
+	}
+	i2c_watermark_write(i2cm);
+	i2c_mode_write(i2cm, port);
+	return 1;
+}
+
+static int _i2c_put(struct i2cm *i2cm, uint16_t port, uint8_t addr,
+			uint16_t size, uint8_t *d)
+{
+	uint32_t fsidata;
+	uint32_t data = 0;
+	int rc = 0;
+
+	if(!i2cm_ok_to_use(i2cm, port)) {
+		rc = 1;
+		return rc;
+	}
+	//TODO: if size > 64kB then use I2C_CMD_READ_CONTINUE and do multiple commands
+	if (size > 64*1024 -1) {
+		PR_ERROR("Can only support up to 64K bytes\n");
+		return -1;
+	}
+
+	/* Set slave device */
+	fsidata = I2C_CMD_WITH_START | I2C_CMD_WITH_ADDR;
+	fsidata = SETFIELD(I2C_CMD_DEV_ADDR, fsidata, addr);
+	fsidata = SETFIELD(I2C_CMD_LENGTH, fsidata, size);
+	_i2cm_reg_write(i2cm, I2C_CMD_REG, fsidata);
+
+	rc = i2c_poll_status(i2cm, &data);
+	if (rc) {
+		PR_ERROR("FAILED to set i2c device\n");
+		return rc;
+	}
+
+	/* Write data into the FIFO of the slave device */
+	i2c_fifo_write(i2cm, (uint32_t *)d, size);
+
+	rc = i2c_poll_status(i2cm, &data);
+	if (rc) {
+		PR_ERROR("FAILED to write all data\n");
+		return rc;
+	}
+	return rc;
+}
+
+static int _i2c_get(struct i2cm *i2cm, uint16_t port, uint8_t addr,
+			uint16_t size, uint8_t *d)
+{
+	uint32_t fsidata;
+	uint32_t data = 0;
+	int rc = 0;
+	int bytes_read;
+
+	if(!i2cm_ok_to_use(i2cm, port)) {
+		rc = 1;
+		return rc;
+	}
+
+	//TODO: if size > 64kB then use I2C_CMD_READ_CONTINUE and do multiple commands
+	if (size > 64*1024 -1) {
+		PR_ERROR("Can only support up to 64K bytes\n");
+		return -1;
+	}
+
+	/* Tell i2c master to read from slave device's fifo */
+	fsidata = I2C_CMD_WITH_START | I2C_CMD_WITH_STOP | I2C_CMD_WITH_ADDR | I2C_CMD_READ_NOT_WRITE;
+	fsidata = SETFIELD(I2C_CMD_DEV_ADDR, fsidata, addr);
+	fsidata = SETFIELD(I2C_CMD_LENGTH, fsidata, size);
+	_i2cm_reg_write(i2cm, I2C_CMD_REG, fsidata);
+
+	bytes_read = i2c_fifo_read(i2cm, (uint32_t*)d, size);
+
+	rc = i2c_poll_status(i2cm, &data);
+	if (rc) {
+		PR_ERROR("Error occured while reading FIFO\n");
+		return rc;
+	}
+
+	if (bytes_read < size) {
+		PR_ERROR("Read %i bytes, expected to read %i bytes\n", bytes_read, size);
+		debug_print_reg(i2cm);
+		return -1;
+	}
+
+	if (data & I2C_STAT_CMD_COMP)
+		rc = 0;
+	else
+		rc = -1;
+	return rc;
+}
+
+static int i2c_get(struct i2cbus *i2cbus, uint8_t addr, uint16_t size, uint8_t *d)
+{
+	struct i2cm *i2cm = target_to_i2cm(i2cbus->target.parent);
+	return _i2c_get(i2cm, i2cbus->port, addr, size, d);
+}
+
+static int i2c_put(struct i2cbus *i2cbus, uint8_t addr, uint16_t size, uint8_t *d)
+{
+	struct i2cm *i2cm = target_to_i2cm(i2cbus->target.parent);
+	return _i2c_put(i2cm, i2cbus->port, addr, size, d);
+}
+
+static int i2cm_target_probe(struct pdbg_target *target)
+{
+	struct i2cbus *i2cbus = target_to_i2cbus(target);
+	i2cbus->port = target->index;
+
+	return 0;
+}
+
+static struct i2cbus i2c_bus_cfam = {
+	.target = {
+		.name =	"CFAM I2C bus",
+		.compatible = "ibm,power9-i2c-port",
+		.class = "i2c_bus",
+		.probe = i2cm_target_probe,
+	},
+	.read = i2c_get,
+	.write = i2c_put,
+};
+DECLARE_HW_UNIT(i2c_bus_cfam);
+
+static struct i2cm i2cm_cfam = {
+	.target = {
+		.name =	"CFAM I2C Master",
+		.compatible = "ibm,fsi-i2c-master",
+		.class = "i2cm",
+	}
+};
+DECLARE_HW_UNIT(i2cm_cfam);
+
+/////////////////////////////////////////////////////////////////////////////
+
 #ifdef ENABLE_I2CLIB
 #include <i2c/smbus.h>
 
diff --git a/libpdbg/libpdbg.h b/libpdbg/libpdbg.h
index c09faa5..7e9b488 100644
--- a/libpdbg/libpdbg.h
+++ b/libpdbg/libpdbg.h
@@ -125,6 +125,8 @@  int fsi_write(struct pdbg_target *target, uint32_t addr, uint32_t val);
 
 int i2c_read(struct pdbg_target *target, uint8_t addr,	uint16_t size,
 			uint8_t *data);
+int i2c_write(struct pdbg_target *target, uint8_t addr, uint16_t size,
+			uint8_t *data);
 
 int pib_read(struct pdbg_target *target, uint64_t addr, uint64_t *val);
 int pib_write(struct pdbg_target *target, uint64_t addr, uint64_t val);
diff --git a/libpdbg/target.c b/libpdbg/target.c
index 2366ed9..21efba9 100644
--- a/libpdbg/target.c
+++ b/libpdbg/target.c
@@ -206,6 +206,17 @@  int i2c_read(struct pdbg_target *i2cm_dt, uint8_t addr, uint16_t size, uint8_t *
 	return i2cbus->read(i2cbus, addr, size, data);
 }
 
+int i2c_write(struct pdbg_target *i2cm_dt, uint8_t addr, uint16_t size, uint8_t *data)
+{
+	struct i2cbus *i2cbus;
+	uint64_t addr64 = addr;
+
+	i2cm_dt = get_class_target_addr(i2cm_dt, "i2c_bus", &addr64);
+	i2cbus = target_to_i2cbus(i2cm_dt);
+
+	return i2cbus->write(i2cbus, addr, size, data);
+}
+
 int fsi_read(struct pdbg_target *fsi_dt, uint32_t addr, uint32_t *data)
 {
 	struct fsi *fsi;
diff --git a/libpdbg/target.h b/libpdbg/target.h
index 814e59a..ca9f401 100644
--- a/libpdbg/target.h
+++ b/libpdbg/target.h
@@ -142,10 +142,15 @@  struct i2cbus {
 	struct pdbg_target target;
 	int (*read)(struct i2cbus *, uint8_t, uint16_t, uint8_t *);
 	int (*write)(struct i2cbus *, uint8_t, uint16_t, uint8_t*);
+	uint8_t port;
 	int i2c_fd;
 };
 #define target_to_i2cbus(x) container_of(x, struct i2cbus, target)
 
+struct i2cm {
+	struct pdbg_target target;
+};
+#define target_to_i2cm(x) container_of(x, struct i2cm, target)
 
 struct core {
 	struct pdbg_target target;
diff --git a/p9-kernel.dts.m4 b/p9-kernel.dts.m4
index cc07682..9973b45 100644
--- a/p9-kernel.dts.m4
+++ b/p9-kernel.dts.m4
@@ -26,7 +26,7 @@ 
 			#address-cells = <0x1>;
 			#size-cells = <0x0>;
 			reg = <0x0 0x1800 0x400>;
-			compatible = "ibm,kernel-i2c-master";
+			compatible = "ibm,fsi-i2c-master";
 			include(p9-i2c.dts.m4)dnl
 		};
 
diff --git a/src/i2c.c b/src/i2c.c
index 5d1fc5b..c3b88a5 100644
--- a/src/i2c.c
+++ b/src/i2c.c
@@ -17,6 +17,8 @@ 
 #include <libpdbg.h>
 #include <inttypes.h>
 #include <stdlib.h>
+#include <stdlib.h>
+#include <unistd.h>
 
 #include "main.h"
 #include "optcmd.h"
@@ -33,6 +35,7 @@  static int geti2c(uint8_t addr, uint16_t size)
 
 	data = malloc(size);
 	assert(data);
+	assert(!(size % 4));
 
 	for_each_path_target_class("i2c_bus", target) {
 		if (pdbg_target_probe(target) != PDBG_TARGET_ENABLED)
@@ -51,3 +54,26 @@  static int geti2c(uint8_t addr, uint16_t size)
 	return 0;
 }
 OPTCMD_DEFINE_CMD_WITH_ARGS(geti2c, geti2c, (DATA8, DATA16));
+
+static int puti2c(uint8_t addr, uint16_t size, uint64_t data)
+{
+	uint8_t *d = (uint8_t *) &data;
+	struct pdbg_target *target, *selected = NULL;
+
+	assert(!(size % 4));
+
+	for_each_path_target_class("i2c_bus", target) {
+		if (pdbg_target_probe(target) != PDBG_TARGET_ENABLED)
+			continue;
+		selected = target;
+		if (i2c_write(target, addr, size, d) == 0)
+			break;
+		break;
+	}
+
+	if (selected == NULL)
+		return -1;
+	printf("wrote %i bytes \n", size);
+	return 0;
+}
+OPTCMD_DEFINE_CMD_WITH_ARGS(puti2c, puti2c, (DATA8, DATA16, DATA));
diff --git a/src/main.c b/src/main.c
index 3441d3c..7e03556 100644
--- a/src/main.c
+++ b/src/main.c
@@ -93,7 +93,7 @@  extern struct optcmd_cmd
 	optcmd_threadstatus, optcmd_sreset, optcmd_regs, optcmd_probe,
 	optcmd_getmem, optcmd_putmem, optcmd_getmemio, optcmd_putmemio,
 	optcmd_getxer, optcmd_putxer, optcmd_getcr, optcmd_putcr,
-	optcmd_gdbserver, optcmd_geti2c;
+	optcmd_gdbserver, optcmd_geti2c, optcmd_puti2c;
 
 static struct optcmd_cmd *cmds[] = {
 	&optcmd_getscom, &optcmd_putscom, &optcmd_getcfam, &optcmd_putcfam,
@@ -103,7 +103,7 @@  static struct optcmd_cmd *cmds[] = {
 	&optcmd_threadstatus, &optcmd_sreset, &optcmd_regs, &optcmd_probe,
 	&optcmd_getmem, &optcmd_putmem, &optcmd_getmemio, &optcmd_putmemio,
 	&optcmd_getxer, &optcmd_putxer, &optcmd_getcr, &optcmd_putcr,
-	&optcmd_gdbserver, &optcmd_geti2c,
+	&optcmd_gdbserver, &optcmd_geti2c, &optcmd_puti2c,
 };
 
 /* Purely for printing usage text. We could integrate printing argument and flag
@@ -146,6 +146,7 @@  static struct action actions[] = {
 	{ "regs",  "[--backtrace]", "State (optionally display backtrace)" },
 	{ "gdbserver", "", "Start a gdb server" },
 	{ "geti2c", "<device> <n>", "Read n bytes from specified device" },
+	{ "puti2c", "<device> <n>", "Write n bytes to the specified device " },
 };
 
 static void print_usage(void)