// SPDX-License-Identifier: BSD-3-Clause
/*
 * Copyright (c) 2020, MIPI Alliance, Inc.
 *
 * Author: Nicolas Pitre <npitre@baylibre.com>
 *
 * I3C HCI v1.0/v1.1 Command Descriptor Handling
 */

#include <linux/bitfield.h>
#include <linux/i3c/master.h>

#include "hci.h"
#include "cmd.h"
#include "dat.h"
#include "dct.h"


/*
 * Address Assignment Command
 */

#define CMD_0_ATTR_A			FIELD_PREP(CMD_0_ATTR, 0x2)

#define CMD_A0_TOC				   W0_BIT_(31)
#define CMD_A0_ROC				   W0_BIT_(30)
#define CMD_A0_DEV_COUNT(v)		FIELD_PREP(W0_MASK(29, 26), v)
#define CMD_A0_DEV_INDEX(v)		FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_A0_CMD(v)			FIELD_PREP(W0_MASK(14,  7), v)
#define CMD_A0_TID(v)			FIELD_PREP(W0_MASK( 6,  3), v)

/*
 * Immediate Data Transfer Command
 */

#define CMD_0_ATTR_I			FIELD_PREP(CMD_0_ATTR, 0x1)

#define CMD_I1_DATA_BYTE_4(v)		FIELD_PREP(W1_MASK(63, 56), v)
#define CMD_I1_DATA_BYTE_3(v)		FIELD_PREP(W1_MASK(55, 48), v)
#define CMD_I1_DATA_BYTE_2(v)		FIELD_PREP(W1_MASK(47, 40), v)
#define CMD_I1_DATA_BYTE_1(v)		FIELD_PREP(W1_MASK(39, 32), v)
#define CMD_I1_DEF_BYTE(v)		FIELD_PREP(W1_MASK(39, 32), v)
#define CMD_I0_TOC				   W0_BIT_(31)
#define CMD_I0_ROC				   W0_BIT_(30)
#define CMD_I0_RNW				   W0_BIT_(29)
#define CMD_I0_MODE(v)			FIELD_PREP(W0_MASK(28, 26), v)
#define CMD_I0_DTT(v)			FIELD_PREP(W0_MASK(25, 23), v)
#define CMD_I0_DEV_INDEX(v)		FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_I0_CP				   W0_BIT_(15)
#define CMD_I0_CMD(v)			FIELD_PREP(W0_MASK(14,  7), v)
#define CMD_I0_TID(v)			FIELD_PREP(W0_MASK( 6,  3), v)

/*
 * Regular Data Transfer Command
 */

#define CMD_0_ATTR_R			FIELD_PREP(CMD_0_ATTR, 0x0)

#define CMD_R1_DATA_LENGTH(v)		FIELD_PREP(W1_MASK(63, 48), v)
#define CMD_R1_DEF_BYTE(v)		FIELD_PREP(W1_MASK(39, 32), v)
#define CMD_R0_TOC				   W0_BIT_(31)
#define CMD_R0_ROC				   W0_BIT_(30)
#define CMD_R0_RNW				   W0_BIT_(29)
#define CMD_R0_MODE(v)			FIELD_PREP(W0_MASK(28, 26), v)
#define CMD_R0_DBP				   W0_BIT_(25)
#define CMD_R0_DEV_INDEX(v)		FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_R0_CP				   W0_BIT_(15)
#define CMD_R0_CMD(v)			FIELD_PREP(W0_MASK(14,  7), v)
#define CMD_R0_TID(v)			FIELD_PREP(W0_MASK( 6,  3), v)

/*
 * Combo Transfer (Write + Write/Read) Command
 */

#define CMD_0_ATTR_C			FIELD_PREP(CMD_0_ATTR, 0x3)

#define CMD_C1_DATA_LENGTH(v)		FIELD_PREP(W1_MASK(63, 48), v)
#define CMD_C1_OFFSET(v)		FIELD_PREP(W1_MASK(47, 32), v)
#define CMD_C0_TOC				   W0_BIT_(31)
#define CMD_C0_ROC				   W0_BIT_(30)
#define CMD_C0_RNW				   W0_BIT_(29)
#define CMD_C0_MODE(v)			FIELD_PREP(W0_MASK(28, 26), v)
#define CMD_C0_16_BIT_SUBOFFSET			   W0_BIT_(25)
#define CMD_C0_FIRST_PHASE_MODE			   W0_BIT_(24)
#define CMD_C0_DATA_LENGTH_POSITION(v)	FIELD_PREP(W0_MASK(23, 22), v)
#define CMD_C0_DEV_INDEX(v)		FIELD_PREP(W0_MASK(20, 16), v)
#define CMD_C0_CP				   W0_BIT_(15)
#define CMD_C0_CMD(v)			FIELD_PREP(W0_MASK(14,  7), v)
#define CMD_C0_TID(v)			FIELD_PREP(W0_MASK( 6,  3), v)

/*
 * Internal Control Command
 */

#define CMD_0_ATTR_M			FIELD_PREP(CMD_0_ATTR, 0x7)

#define CMD_M1_VENDOR_SPECIFIC			   W1_MASK(63, 32)
#define CMD_M0_MIPI_RESERVED			   W0_MASK(31, 12)
#define CMD_M0_MIPI_CMD				   W0_MASK(11,  8)
#define CMD_M0_VENDOR_INFO_PRESENT		   W0_BIT_( 7)
#define CMD_M0_TID(v)			FIELD_PREP(W0_MASK( 6,  3), v)


/* Data Transfer Speed and Mode */
enum hci_cmd_mode {
	MODE_I3C_SDR0		= 0x0,
	MODE_I3C_SDR1		= 0x1,
	MODE_I3C_SDR2		= 0x2,
	MODE_I3C_SDR3		= 0x3,
	MODE_I3C_SDR4		= 0x4,
	MODE_I3C_HDR_TSx	= 0x5,
	MODE_I3C_HDR_DDR	= 0x6,
	MODE_I3C_HDR_BT		= 0x7,
	MODE_I3C_Fm_FmP		= 0x8,
	MODE_I2C_Fm		= 0x0,
	MODE_I2C_FmP		= 0x1,
	MODE_I2C_UD1		= 0x2,
	MODE_I2C_UD2		= 0x3,
	MODE_I2C_UD3		= 0x4,
};

static enum hci_cmd_mode get_i3c_mode(struct i3c_hci *hci)
{
	struct i3c_bus *bus = i3c_master_get_bus(&hci->master);

	if (bus->scl_rate.i3c >= 12500000)
		return MODE_I3C_SDR0;
	if (bus->scl_rate.i3c > 8000000)
		return MODE_I3C_SDR1;
	if (bus->scl_rate.i3c > 6000000)
		return MODE_I3C_SDR2;
	if (bus->scl_rate.i3c > 4000000)
		return MODE_I3C_SDR3;
	if (bus->scl_rate.i3c > 2000000)
		return MODE_I3C_SDR4;
	return MODE_I3C_Fm_FmP;
}

static enum hci_cmd_mode get_i2c_mode(struct i3c_hci *hci)
{
	struct i3c_bus *bus = i3c_master_get_bus(&hci->master);

	if (bus->scl_rate.i2c >= 1000000)
		return MODE_I2C_FmP;
	return MODE_I2C_Fm;
}

static void fill_data_bytes(struct hci_xfer *xfer, u8 *data,
			    unsigned int data_len)
{
	xfer->cmd_desc[1] = 0;
	switch (data_len) {
	case 4:
		xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_4(data[3]);
		fallthrough;
	case 3:
		xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_3(data[2]);
		fallthrough;
	case 2:
		xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_2(data[1]);
		fallthrough;
	case 1:
		xfer->cmd_desc[1] |= CMD_I1_DATA_BYTE_1(data[0]);
		fallthrough;
	case 0:
		break;
	}
	/* we consumed all the data with the cmd descriptor */
	xfer->data = NULL;
}

static int hci_cmd_v1_prep_ccc(struct i3c_hci *hci,
			       struct hci_xfer *xfer,
			       u8 ccc_addr, u8 ccc_cmd, bool raw)
{
	unsigned int dat_idx = 0;
	enum hci_cmd_mode mode = get_i3c_mode(hci);
	u8 *data = xfer->data;
	unsigned int data_len = xfer->data_len;
	bool rnw = xfer->rnw;
	int ret;

	/* this should never happen */
	if (WARN_ON(raw))
		return -EINVAL;

	if (ccc_addr != I3C_BROADCAST_ADDR) {
		ret = mipi_i3c_hci_dat_v1.get_index(hci, ccc_addr);
		if (ret < 0)
			return ret;
		dat_idx = ret;
	}

	xfer->cmd_tid = hci_get_tid();

	if (!rnw && data_len <= 4) {
		/* we use an Immediate Data Transfer Command */
		xfer->cmd_desc[0] =
			CMD_0_ATTR_I |
			CMD_I0_TID(xfer->cmd_tid) |
			CMD_I0_CMD(ccc_cmd) | CMD_I0_CP |
			CMD_I0_DEV_INDEX(dat_idx) |
			CMD_I0_DTT(data_len) |
			CMD_I0_MODE(mode);
		fill_data_bytes(xfer, data, data_len);
	} else {
		/* we use a Regular Data Transfer Command */
		xfer->cmd_desc[0] =
			CMD_0_ATTR_R |
			CMD_R0_TID(xfer->cmd_tid) |
			CMD_R0_CMD(ccc_cmd) | CMD_R0_CP |
			CMD_R0_DEV_INDEX(dat_idx) |
			CMD_R0_MODE(mode) |
			(rnw ? CMD_R0_RNW : 0);
		xfer->cmd_desc[1] =
			CMD_R1_DATA_LENGTH(data_len);
	}

	return 0;
}

static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci,
				     struct i3c_dev_desc *dev,
				     struct hci_xfer *xfer)
{
	struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev);
	unsigned int dat_idx = dev_data->dat_idx;
	enum hci_cmd_mode mode = get_i3c_mode(hci);
	u8 *data = xfer->data;
	unsigned int data_len = xfer->data_len;
	bool rnw = xfer->rnw;

	xfer->cmd_tid = hci_get_tid();

	if (!rnw && data_len <= 4) {
		/* we use an Immediate Data Transfer Command */
		xfer->cmd_desc[0] =
			CMD_0_ATTR_I |
			CMD_I0_TID(xfer->cmd_tid) |
			CMD_I0_DEV_INDEX(dat_idx) |
			CMD_I0_DTT(data_len) |
			CMD_I0_MODE(mode);
		fill_data_bytes(xfer, data, data_len);
	} else {
		/* we use a Regular Data Transfer Command */
		xfer->cmd_desc[0] =
			CMD_0_ATTR_R |
			CMD_R0_TID(xfer->cmd_tid) |
			CMD_R0_DEV_INDEX(dat_idx) |
			CMD_R0_MODE(mode) |
			(rnw ? CMD_R0_RNW : 0);
		xfer->cmd_desc[1] =
			CMD_R1_DATA_LENGTH(data_len);
	}
}

static void hci_cmd_v1_prep_i2c_xfer(struct i3c_hci *hci,
				     struct i2c_dev_desc *dev,
				     struct hci_xfer *xfer)
{
	struct i3c_hci_dev_data *dev_data = i2c_dev_get_master_data(dev);
	unsigned int dat_idx = dev_data->dat_idx;
	enum hci_cmd_mode mode = get_i2c_mode(hci);
	u8 *data = xfer->data;
	unsigned int data_len = xfer->data_len;
	bool rnw = xfer->rnw;

	xfer->cmd_tid = hci_get_tid();

	if (!rnw && data_len <= 4) {
		/* we use an Immediate Data Transfer Command */
		xfer->cmd_desc[0] =
			CMD_0_ATTR_I |
			CMD_I0_TID(xfer->cmd_tid) |
			CMD_I0_DEV_INDEX(dat_idx) |
			CMD_I0_DTT(data_len) |
			CMD_I0_MODE(mode);
		fill_data_bytes(xfer, data, data_len);
	} else {
		/* we use a Regular Data Transfer Command */
		xfer->cmd_desc[0] =
			CMD_0_ATTR_R |
			CMD_R0_TID(xfer->cmd_tid) |
			CMD_R0_DEV_INDEX(dat_idx) |
			CMD_R0_MODE(mode) |
			(rnw ? CMD_R0_RNW : 0);
		xfer->cmd_desc[1] =
			CMD_R1_DATA_LENGTH(data_len);
	}
}

static int hci_cmd_v1_daa(struct i3c_hci *hci)
{
	struct hci_xfer *xfer;
	int ret, dat_idx = -1;
	u8 next_addr = 0;
	u64 pid;
	unsigned int dcr, bcr;
	DECLARE_COMPLETION_ONSTACK(done);

	xfer = hci_alloc_xfer(2);
	if (!xfer)
		return -ENOMEM;

	/*
	 * Simple for now: we allocate a temporary DAT entry, do a single
	 * DAA, register the device which will allocate its own DAT entry
	 * via the core callback, then free the temporary DAT entry.
	 * Loop until there is no more devices to assign an address to.
	 * Yes, there is room for improvements.
	 */
	for (;;) {
		ret = mipi_i3c_hci_dat_v1.alloc_entry(hci);
		if (ret < 0)
			break;
		dat_idx = ret;
		ret = i3c_master_get_free_addr(&hci->master, next_addr);
		if (ret < 0)
			break;
		next_addr = ret;

		DBG("next_addr = 0x%02x, DAA using DAT %d", next_addr, dat_idx);
		mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, dat_idx, next_addr);
		mipi_i3c_hci_dct_index_reset(hci);

		xfer->cmd_tid = hci_get_tid();
		xfer->cmd_desc[0] =
			CMD_0_ATTR_A |
			CMD_A0_TID(xfer->cmd_tid) |
			CMD_A0_CMD(I3C_CCC_ENTDAA) |
			CMD_A0_DEV_INDEX(dat_idx) |
			CMD_A0_DEV_COUNT(1) |
			CMD_A0_ROC | CMD_A0_TOC;
		xfer->cmd_desc[1] = 0;
		hci->io->queue_xfer(hci, xfer, 1);
		if (!wait_for_completion_timeout(&done, HZ) &&
		    hci->io->dequeue_xfer(hci, xfer, 1)) {
			ret = -ETIME;
			break;
		}
		if (RESP_STATUS(xfer[0].response) == RESP_ERR_NACK &&
		    RESP_STATUS(xfer[0].response) == 1) {
			ret = 0;  /* no more devices to be assigned */
			break;
		}
		if (RESP_STATUS(xfer[0].response) != RESP_SUCCESS) {
			ret = -EIO;
			break;
		}

		i3c_hci_dct_get_val(hci, 0, &pid, &dcr, &bcr);
		DBG("assigned address %#x to device PID=0x%llx DCR=%#x BCR=%#x",
		    next_addr, pid, dcr, bcr);

		mipi_i3c_hci_dat_v1.free_entry(hci, dat_idx);
		dat_idx = -1;

		/*
		 * TODO: Extend the subsystem layer to allow for registering
		 * new device and provide BCR/DCR/PID at the same time.
		 */
		ret = i3c_master_add_i3c_dev_locked(&hci->master, next_addr);
		if (ret)
			break;
	}

	if (dat_idx >= 0)
		mipi_i3c_hci_dat_v1.free_entry(hci, dat_idx);
	hci_free_xfer(xfer, 1);
	return ret;
}

const struct hci_cmd_ops mipi_i3c_hci_cmd_v1 = {
	.prep_ccc		= hci_cmd_v1_prep_ccc,
	.prep_i3c_xfer		= hci_cmd_v1_prep_i3c_xfer,
	.prep_i2c_xfer		= hci_cmd_v1_prep_i2c_xfer,
	.perform_daa		= hci_cmd_v1_daa,
};
