All of lore.kernel.org
 help / color / mirror / Atom feed
From: Wolfram Sang <wsa@the-dreams.de>
To: Radu Pirea <radu_nicolae.pirea@upb.ro>
Cc: linux-arm-kernel@lists.infradead.org, linux-i2c@vger.kernel.org,
	linux-kernel@vger.kernel.org, michal.simek@xilinx.com,
	shubhrajyoti.datta@gmail.com,
	Chirag Parekh <chirag.parekh@xilinx.com>
Subject: Re: [PATCH v2] i2c: cadence: Added slave support
Date: Wed, 15 Apr 2020 14:58:50 +0200	[thread overview]
Message-ID: <20200415125850.GD910@ninjato> (raw)
In-Reply-To: <20200106104336.101987-1-radu_nicolae.pirea@upb.ro>

[-- Attachment #1: Type: text/plain, Size: 15056 bytes --]

On Mon, Jan 06, 2020 at 12:43:36PM +0200, Radu Pirea wrote:
> Added support for I2C slave functionality
> 
> Signed-off-by: Chirag Parekh <chirag.parekh@xilinx.com>
> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
> Signed-off-by: Radu Pirea <radu_nicolae.pirea@upb.ro>

Michal, do you want a second look or is your SoB good as-is?

> ---
> 
> Hi,
> 
> This patch implements the slave interface for the i2c cadence driver. Most of the
> work has been done by the guys from Xilinx. All I have done it was to port the
> patch to the upstream kernel, test it and fix some minor issues.
> 
> Any suggestion about how can I improve this patch is welcome.
> 
> Thanks.
> 
> Radu P.
> 
> Changes in v2:
> - guard id->ctrl_reg_diva_divb with IS_ENABLED(CONFIG_I2C_SLAVE) in 
> cdns_i2c_setclk function
> 
>  drivers/i2c/busses/i2c-cadence.c | 320 +++++++++++++++++++++++++++++--
>  1 file changed, 309 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
> index 9d71ce15db05..fc5d6bfa6fd5 100644
> --- a/drivers/i2c/busses/i2c-cadence.c
> +++ b/drivers/i2c/busses/i2c-cadence.c
> @@ -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 */
> @@ -117,6 +134,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
>   *
> @@ -138,6 +181,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;
> @@ -158,6 +205,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 {
> @@ -186,17 +239,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;
> @@ -352,6 +543,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
> @@ -572,10 +784,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;
> @@ -634,7 +864,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;
> @@ -648,14 +886,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 |
> -		(I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
> -		I2C_FUNC_SMBUS_BLOCK_DATA;
> +	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
>  };
>  
>  /**
> @@ -750,7 +1041,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;
>  }
>  
> @@ -943,8 +1237,12 @@ static int cdns_i2c_probe(struct platform_device *pdev)
>  	if (ret || (id->i2c_clk > CDNS_I2C_SPEED_MAX))
>  		id->i2c_clk = CDNS_I2C_SPEED_DEFAULT;
>  
> -	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) {
> -- 
> 2.24.1
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

WARNING: multiple messages have this Message-ID (diff)
From: Wolfram Sang <wsa@the-dreams.de>
To: Radu Pirea <radu_nicolae.pirea@upb.ro>
Cc: Chirag Parekh <chirag.parekh@xilinx.com>,
	shubhrajyoti.datta@gmail.com, michal.simek@xilinx.com,
	linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org
Subject: Re: [PATCH v2] i2c: cadence: Added slave support
Date: Wed, 15 Apr 2020 14:58:50 +0200	[thread overview]
Message-ID: <20200415125850.GD910@ninjato> (raw)
In-Reply-To: <20200106104336.101987-1-radu_nicolae.pirea@upb.ro>


[-- Attachment #1.1: Type: text/plain, Size: 15056 bytes --]

On Mon, Jan 06, 2020 at 12:43:36PM +0200, Radu Pirea wrote:
> Added support for I2C slave functionality
> 
> Signed-off-by: Chirag Parekh <chirag.parekh@xilinx.com>
> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
> Signed-off-by: Radu Pirea <radu_nicolae.pirea@upb.ro>

Michal, do you want a second look or is your SoB good as-is?

> ---
> 
> Hi,
> 
> This patch implements the slave interface for the i2c cadence driver. Most of the
> work has been done by the guys from Xilinx. All I have done it was to port the
> patch to the upstream kernel, test it and fix some minor issues.
> 
> Any suggestion about how can I improve this patch is welcome.
> 
> Thanks.
> 
> Radu P.
> 
> Changes in v2:
> - guard id->ctrl_reg_diva_divb with IS_ENABLED(CONFIG_I2C_SLAVE) in 
> cdns_i2c_setclk function
> 
>  drivers/i2c/busses/i2c-cadence.c | 320 +++++++++++++++++++++++++++++--
>  1 file changed, 309 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
> index 9d71ce15db05..fc5d6bfa6fd5 100644
> --- a/drivers/i2c/busses/i2c-cadence.c
> +++ b/drivers/i2c/busses/i2c-cadence.c
> @@ -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 */
> @@ -117,6 +134,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
>   *
> @@ -138,6 +181,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;
> @@ -158,6 +205,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 {
> @@ -186,17 +239,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;
> @@ -352,6 +543,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
> @@ -572,10 +784,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;
> @@ -634,7 +864,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;
> @@ -648,14 +886,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 |
> -		(I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
> -		I2C_FUNC_SMBUS_BLOCK_DATA;
> +	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
>  };
>  
>  /**
> @@ -750,7 +1041,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;
>  }
>  
> @@ -943,8 +1237,12 @@ static int cdns_i2c_probe(struct platform_device *pdev)
>  	if (ret || (id->i2c_clk > CDNS_I2C_SPEED_MAX))
>  		id->i2c_clk = CDNS_I2C_SPEED_DEFAULT;
>  
> -	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) {
> -- 
> 2.24.1
> 

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

  reply	other threads:[~2020-04-15 12:59 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-01-06 10:43 [PATCH v2] i2c: cadence: Added slave support Radu Pirea
2020-01-06 10:43 ` Radu Pirea
2020-04-15 12:58 ` Wolfram Sang [this message]
2020-04-15 12:58   ` Wolfram Sang
2020-04-15 14:20   ` Michal Simek
2020-04-15 14:20     ` Michal Simek
2020-04-15 16:22     ` Wolfram Sang
2020-04-15 16:22       ` Wolfram Sang
2020-04-18 21:36 ` Wolfram Sang
2020-04-18 21:36   ` Wolfram Sang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200415125850.GD910@ninjato \
    --to=wsa@the-dreams.de \
    --cc=chirag.parekh@xilinx.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=michal.simek@xilinx.com \
    --cc=radu_nicolae.pirea@upb.ro \
    --cc=shubhrajyoti.datta@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.