Commit 1a351b10 authored by Radu Pirea's avatar Radu Pirea Committed by Wolfram Sang
Browse files

i2c: cadence: Added slave support



Added support for I2C slave functionality

Signed-off-by: default avatarChirag Parekh <chirag.parekh@xilinx.com>
Signed-off-by: default avatarMichal Simek <michal.simek@xilinx.com>
Signed-off-by: default avatarRadu Pirea <radu_nicolae.pirea@upb.ro>
Signed-off-by: default avatarWolfram Sang <wsa@the-dreams.de>
parent 35eba185
Loading
Loading
Loading
Loading
+309 −11
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@
#define CDNS_I2C_ISR_OFFSET		0x10 /* IRQ Status Register, RW */
#define CDNS_I2C_XFER_SIZE_OFFSET	0x14 /* Transfer Size Register, RW */
#define CDNS_I2C_TIME_OUT_OFFSET	0x1C /* Time Out Register, RW */
#define CDNS_I2C_IMR_OFFSET		0x20 /* IRQ Mask Register, RO */
#define CDNS_I2C_IER_OFFSET		0x24 /* IRQ Enable Register, WO */
#define CDNS_I2C_IDR_OFFSET		0x28 /* IRQ Disable Register, WO */

@@ -40,9 +41,17 @@
#define CDNS_I2C_CR_DIVB_SHIFT		8
#define CDNS_I2C_CR_DIVB_MASK		(0x3f << CDNS_I2C_CR_DIVB_SHIFT)

#define CDNS_I2C_CR_MASTER_EN_MASK	(CDNS_I2C_CR_NEA | \
					 CDNS_I2C_CR_ACK_EN | \
					 CDNS_I2C_CR_MS)

#define CDNS_I2C_CR_SLAVE_EN_MASK	~CDNS_I2C_CR_MASTER_EN_MASK

/* Status Register Bit mask definitions */
#define CDNS_I2C_SR_BA		BIT(8)
#define CDNS_I2C_SR_TXDV	BIT(6)
#define CDNS_I2C_SR_RXDV	BIT(5)
#define CDNS_I2C_SR_RXRW	BIT(3)

/*
 * I2C Address Register Bit mask definitions
@@ -91,6 +100,14 @@
					 CDNS_I2C_IXR_DATA | \
					 CDNS_I2C_IXR_COMP)

#define CDNS_I2C_IXR_SLAVE_INTR_MASK	(CDNS_I2C_IXR_RX_UNF | \
					 CDNS_I2C_IXR_TX_OVF | \
					 CDNS_I2C_IXR_RX_OVF | \
					 CDNS_I2C_IXR_TO | \
					 CDNS_I2C_IXR_NACK | \
					 CDNS_I2C_IXR_DATA | \
					 CDNS_I2C_IXR_COMP)

#define CDNS_I2C_TIMEOUT		msecs_to_jiffies(1000)
/* timeout for pm runtime autosuspend */
#define CNDS_I2C_PM_TIMEOUT		1000	/* ms */
@@ -114,6 +131,32 @@
#define cdns_i2c_readreg(offset)       readl_relaxed(id->membase + offset)
#define cdns_i2c_writereg(val, offset) writel_relaxed(val, id->membase + offset)

#if IS_ENABLED(CONFIG_I2C_SLAVE)
/**
 * enum cdns_i2c_mode - I2C Controller current operating mode
 *
 * @CDNS_I2C_MODE_SLAVE:       I2C controller operating in slave mode
 * @CDNS_I2C_MODE_MASTER:      I2C Controller operating in master mode
 */
enum cdns_i2c_mode {
	CDNS_I2C_MODE_SLAVE,
	CDNS_I2C_MODE_MASTER,
};

/**
 * enum cdns_i2c_slave_mode - Slave state when I2C is operating in slave mode
 *
 * @CDNS_I2C_SLAVE_STATE_IDLE: I2C slave idle
 * @CDNS_I2C_SLAVE_STATE_SEND: I2C slave sending data to master
 * @CDNS_I2C_SLAVE_STATE_RECV: I2C slave receiving data from master
 */
enum cdns_i2c_slave_state {
	CDNS_I2C_SLAVE_STATE_IDLE,
	CDNS_I2C_SLAVE_STATE_SEND,
	CDNS_I2C_SLAVE_STATE_RECV,
};
#endif

/**
 * struct cdns_i2c - I2C device private data structure
 *
@@ -135,6 +178,10 @@
 * @clk:		Pointer to struct clk
 * @clk_rate_change_nb:	Notifier block for clock rate changes
 * @quirks:		flag for broken hold bit usage in r1p10
 * @ctrl_reg_diva_divb: value of fields DIV_A and DIV_B from CR register
 * @slave:		Registered slave instance.
 * @dev_mode:		I2C operating role(master/slave).
 * @slave_state:	I2C Slave state(idle/read/write).
 */
struct cdns_i2c {
	struct device		*dev;
@@ -155,6 +202,12 @@ struct cdns_i2c {
	struct clk *clk;
	struct notifier_block clk_rate_change_nb;
	u32 quirks;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	u16 ctrl_reg_diva_divb;
	struct i2c_client *slave;
	enum cdns_i2c_mode dev_mode;
	enum cdns_i2c_slave_state slave_state;
#endif
};

struct cdns_platform_data {
@@ -183,17 +236,155 @@ static inline bool cdns_is_holdquirk(struct cdns_i2c *id, bool hold_wrkaround)
		(id->curr_recv_count == CDNS_I2C_FIFO_DEPTH + 1));
}

#if IS_ENABLED(CONFIG_I2C_SLAVE)
static void cdns_i2c_set_mode(enum cdns_i2c_mode mode, struct cdns_i2c *id)
{
	/* Disable all interrupts */
	cdns_i2c_writereg(CDNS_I2C_IXR_ALL_INTR_MASK, CDNS_I2C_IDR_OFFSET);

	/* Clear FIFO and transfer size */
	cdns_i2c_writereg(CDNS_I2C_CR_CLR_FIFO, CDNS_I2C_CR_OFFSET);

	/* Update device mode and state */
	id->dev_mode = mode;
	id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;

	switch (mode) {
	case CDNS_I2C_MODE_MASTER:
		/* Enable i2c master */
		cdns_i2c_writereg(id->ctrl_reg_diva_divb |
				  CDNS_I2C_CR_MASTER_EN_MASK,
				  CDNS_I2C_CR_OFFSET);
		/*
		 * This delay is needed to give the IP some time to switch to
		 * the master mode. With lower values(like 110 us) i2cdetect
		 * will not detect any slave and without this delay, the IP will
		 * trigger a timeout interrupt.
		 */
		usleep_range(115, 125);
		break;
	case CDNS_I2C_MODE_SLAVE:
		/* Enable i2c slave */
		cdns_i2c_writereg(id->ctrl_reg_diva_divb &
				  CDNS_I2C_CR_SLAVE_EN_MASK,
				  CDNS_I2C_CR_OFFSET);

		/* Setting slave address */
		cdns_i2c_writereg(id->slave->addr & CDNS_I2C_ADDR_MASK,
				  CDNS_I2C_ADDR_OFFSET);

		/* Enable slave send/receive interrupts */
		cdns_i2c_writereg(CDNS_I2C_IXR_SLAVE_INTR_MASK,
				  CDNS_I2C_IER_OFFSET);
		break;
	}
}

static void cdns_i2c_slave_rcv_data(struct cdns_i2c *id)
{
	u8 bytes;
	unsigned char data;

	/* Prepare backend for data reception */
	if (id->slave_state == CDNS_I2C_SLAVE_STATE_IDLE) {
		id->slave_state = CDNS_I2C_SLAVE_STATE_RECV;
		i2c_slave_event(id->slave, I2C_SLAVE_WRITE_REQUESTED, NULL);
	}

	/* Fetch number of bytes to receive */
	bytes = cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET);

	/* Read data and send to backend */
	while (bytes--) {
		data = cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);
		i2c_slave_event(id->slave, I2C_SLAVE_WRITE_RECEIVED, &data);
	}
}

static void cdns_i2c_slave_send_data(struct cdns_i2c *id)
{
	u8 data;

	/* Prepare backend for data transmission */
	if (id->slave_state == CDNS_I2C_SLAVE_STATE_IDLE) {
		id->slave_state = CDNS_I2C_SLAVE_STATE_SEND;
		i2c_slave_event(id->slave, I2C_SLAVE_READ_REQUESTED, &data);
	} else {
		i2c_slave_event(id->slave, I2C_SLAVE_READ_PROCESSED, &data);
	}

	/* Send data over bus */
	cdns_i2c_writereg(data, CDNS_I2C_DATA_OFFSET);
}

/**
 * cdns_i2c_isr - Interrupt handler for the I2C device
 * @irq:	irq number for the I2C device
 * @ptr:	void pointer to cdns_i2c structure
 * cdns_i2c_slave_isr - Interrupt handler for the I2C device in slave role
 * @ptr:       Pointer to I2C device private data
 *
 * This function handles the data interrupt and transfer complete interrupt of
 * the I2C device in slave role.
 *
 * Return: IRQ_HANDLED always
 */
static irqreturn_t cdns_i2c_slave_isr(void *ptr)
{
	struct cdns_i2c *id = ptr;
	unsigned int isr_status, i2c_status;

	/* Fetch the interrupt status */
	isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);
	cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);

	/* Ignore masked interrupts */
	isr_status &= ~cdns_i2c_readreg(CDNS_I2C_IMR_OFFSET);

	/* Fetch transfer mode (send/receive) */
	i2c_status = cdns_i2c_readreg(CDNS_I2C_SR_OFFSET);

	/* Handle data send/receive */
	if (i2c_status & CDNS_I2C_SR_RXRW) {
		/* Send data to master */
		if (isr_status & CDNS_I2C_IXR_DATA)
			cdns_i2c_slave_send_data(id);

		if (isr_status & CDNS_I2C_IXR_COMP) {
			id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
			i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);
		}
	} else {
		/* Receive data from master */
		if (isr_status & CDNS_I2C_IXR_DATA)
			cdns_i2c_slave_rcv_data(id);

		if (isr_status & CDNS_I2C_IXR_COMP) {
			cdns_i2c_slave_rcv_data(id);
			id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
			i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);
		}
	}

	/* Master indicated xfer stop or fifo underflow/overflow */
	if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_RX_OVF |
			  CDNS_I2C_IXR_RX_UNF | CDNS_I2C_IXR_TX_OVF)) {
		id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
		i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);
		cdns_i2c_writereg(CDNS_I2C_CR_CLR_FIFO, CDNS_I2C_CR_OFFSET);
	}

	return IRQ_HANDLED;
}
#endif

/**
 * cdns_i2c_master_isr - Interrupt handler for the I2C device in master role
 * @ptr:       Pointer to I2C device private data
 *
 * This function handles the data interrupt, transfer complete interrupt and
 * the error interrupts of the I2C device.
 * the error interrupts of the I2C device in master role.
 *
 * Return: IRQ_HANDLED always
 */
static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
static irqreturn_t cdns_i2c_master_isr(void *ptr)
{
	unsigned int isr_status, avail_bytes, updatetx;
	unsigned int bytes_to_send;
@@ -357,6 +548,27 @@ static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
	return status;
}

/**
 * cdns_i2c_isr - Interrupt handler for the I2C device
 * @irq:	irq number for the I2C device
 * @ptr:	void pointer to cdns_i2c structure
 *
 * This function passes the control to slave/master based on current role of
 * i2c controller.
 *
 * Return: IRQ_HANDLED always
 */
static irqreturn_t cdns_i2c_isr(int irq, void *ptr)
{
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	struct cdns_i2c *id = ptr;

	if (id->dev_mode == CDNS_I2C_MODE_SLAVE)
		return cdns_i2c_slave_isr(ptr);
#endif
	return cdns_i2c_master_isr(ptr);
}

/**
 * cdns_i2c_mrecv - Prepare and start a master receive operation
 * @id:		pointer to the i2c device structure
@@ -577,10 +789,28 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
	u32 reg;
	struct cdns_i2c *id = adap->algo_data;
	bool hold_quirk;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	bool change_role = false;
#endif

	ret = pm_runtime_get_sync(id->dev);
	if (ret < 0)
		return ret;

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	/* Check i2c operating mode and switch if possible */
	if (id->dev_mode == CDNS_I2C_MODE_SLAVE) {
		if (id->slave_state != CDNS_I2C_SLAVE_STATE_IDLE)
			return -EAGAIN;

		/* Set mode to master */
		cdns_i2c_set_mode(CDNS_I2C_MODE_MASTER, id);

		/* Mark flag to change role once xfer is completed */
		change_role = true;
	}
#endif

	/* Check if the bus is free */
	if (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) & CDNS_I2C_SR_BA) {
		ret = -EAGAIN;
@@ -639,7 +869,15 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
	}

	ret = num;

out:

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	/* Switch i2c mode to slave */
	if (change_role)
		cdns_i2c_set_mode(CDNS_I2C_MODE_SLAVE, id);
#endif

	pm_runtime_mark_last_busy(id->dev);
	pm_runtime_put_autosuspend(id->dev);
	return ret;
@@ -653,14 +891,67 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
 */
static u32 cdns_i2c_func(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
	u32 func = I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
			(I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
			I2C_FUNC_SMBUS_BLOCK_DATA;

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	func |= I2C_FUNC_SLAVE;
#endif

	return func;
}

#if IS_ENABLED(CONFIG_I2C_SLAVE)
static int cdns_reg_slave(struct i2c_client *slave)
{
	int ret;
	struct cdns_i2c *id = container_of(slave->adapter, struct cdns_i2c,
									adap);

	if (id->slave)
		return -EBUSY;

	if (slave->flags & I2C_CLIENT_TEN)
		return -EAFNOSUPPORT;

	ret = pm_runtime_get_sync(id->dev);
	if (ret < 0)
		return ret;

	/* Store slave information */
	id->slave = slave;

	/* Enable I2C slave */
	cdns_i2c_set_mode(CDNS_I2C_MODE_SLAVE, id);

	return 0;
}

static int cdns_unreg_slave(struct i2c_client *slave)
{
	struct cdns_i2c *id = container_of(slave->adapter, struct cdns_i2c,
									adap);

	pm_runtime_put(id->dev);

	/* Remove slave information */
	id->slave = NULL;

	/* Enable I2C master */
	cdns_i2c_set_mode(CDNS_I2C_MODE_MASTER, id);

	return 0;
}
#endif

static const struct i2c_algorithm cdns_i2c_algo = {
	.master_xfer	= cdns_i2c_master_xfer,
	.functionality	= cdns_i2c_func,
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	.reg_slave	= cdns_reg_slave,
	.unreg_slave	= cdns_unreg_slave,
#endif
};

/**
@@ -755,7 +1046,10 @@ static int cdns_i2c_setclk(unsigned long clk_in, struct cdns_i2c *id)
	ctrl_reg |= ((div_a << CDNS_I2C_CR_DIVA_SHIFT) |
			(div_b << CDNS_I2C_CR_DIVB_SHIFT));
	cdns_i2c_writereg(ctrl_reg, CDNS_I2C_CR_OFFSET);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	id->ctrl_reg_diva_divb = ctrl_reg & (CDNS_I2C_CR_DIVA_MASK |
				 CDNS_I2C_CR_DIVB_MASK);
#endif
	return 0;
}

@@ -948,8 +1242,12 @@ static int cdns_i2c_probe(struct platform_device *pdev)
	if (ret || (id->i2c_clk > I2C_MAX_FAST_MODE_FREQ))
		id->i2c_clk = I2C_MAX_STANDARD_MODE_FREQ;

	cdns_i2c_writereg(CDNS_I2C_CR_ACK_EN | CDNS_I2C_CR_NEA | CDNS_I2C_CR_MS,
			  CDNS_I2C_CR_OFFSET);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	/* Set initial mode to master */
	id->dev_mode = CDNS_I2C_MODE_MASTER;
	id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;
#endif
	cdns_i2c_writereg(CDNS_I2C_CR_MASTER_EN_MASK, CDNS_I2C_CR_OFFSET);

	ret = cdns_i2c_setclk(id->input_clk, id);
	if (ret) {