Re: [PATCH V4] i2c: imx-lpi2c: add target mode support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Carlos,

On Thu, Sep 12, 2024 at 04:24:13PM GMT, carlos.song@xxxxxxx wrote:
> From: Carlos Song <carlos.song@xxxxxxx>
> 
> LPI2C support master controller and target controller enabled
> simultaneously. Both controllers share same SDA/SCL lines and

/same/the same/

> interrupt source but has separate control and status registers.

/separate/a separate/

> Even if target mode is enabled, LPI2C can still work normally
> as master controller at the same time.

It's not what happens in the irq handler, though (I left a
comment in irq handler).

> This patch supports basic target data read/write operations in
> 7-bit target address. LPI2C target mode can be enabled by using
> I2C slave backend. I2C slave backend behave like a standard I2C

/behave/behaves/

> client. For simple use and test, Linux I2C slave EEPROM backend
> can be used.
> 
> Signed-off-by: Carlos Song <carlos.song@xxxxxxx>

...

> +static irqreturn_t lpi2c_imx_isr(int irq, void *dev_id)
> +{
> +	struct lpi2c_imx_struct *lpi2c_imx = dev_id;
> +
> +	if (lpi2c_imx->target) {
> +		u32 scr = readl(lpi2c_imx->base + LPI2C_SCR);
> +		u32 ssr = readl(lpi2c_imx->base + LPI2C_SSR);
> +		u32 sier_filter = ssr & readl(lpi2c_imx->base + LPI2C_SIER);
> +
> +		/* Target is enabled and trigger irq then enter target irq handler */

This sentence is a bit hard to understand, how about:

		/*
		 * The target is enabled and an interrupt has
		 * been triggered. Enter the target's irq handler.
		 */

> +		if ((scr & SCR_SEN) && sier_filter)
> +			return lpi2c_imx_target_isr(lpi2c_imx, ssr, sier_filter);

Can't the interrupt be generated by the master if
lpi2c_imx->target is assigned?

In the git log you are describing a different behavior.

> +	}
> +
> +	/* Otherwise triggered by master then handle irq in master handler */

Otherwise the interrupt has been triggered by the master. Enter
the master's irq handler.

> +	return lpi2c_imx_master_isr(lpi2c_imx);
> +}
> +
> +static void lpi2c_imx_target_init(struct lpi2c_imx_struct *lpi2c_imx)
> +{
> +	u32 temp;
> +
> +	/* reset target module */
> +	writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR);
> +	writel(0, lpi2c_imx->base + LPI2C_SCR);
> +
> +	/* Set target addr */

/addr/address/

> +	writel((lpi2c_imx->target->addr << 1), lpi2c_imx->base + LPI2C_SAMR);
> +
> +	writel(SCFGR1_RXSTALL | SCFGR1_TXDSTALL, lpi2c_imx->base + LPI2C_SCFGR1);
> +
> +	/*
> +	 * set SCFGR2: FILTSDA, FILTSCL and CLKHOLD
> +	 *
> +	 * FILTSCL/FILTSDA can eliminate signal skew. It should generally be
> +	 * set to the same value and should be set >= 50ns.
> +	 *
> +	 * CLKHOLD is only used when clock stretching is enabled, but it will
> +	 * extend the clock stretching to ensure there is an additional delay
> +	 * between the target driving SDA and the target releasing the SCL pin.
> +	 *
> +	 * CLKHOLD setting is crucial for lpi2c target. When master read data
> +	 * from target, if there is a delay caused by cpu idle, excessive load,
> +	 * or other delays between two bytes in one message transmission. so it
> +	 * will cause a short interval time between the driving SDA signal and

/transmission. so it will/transmittion, it will/

> +	 * releasing SCL signal. Lpi2c master will mistakenly think it is a stop

/Lpi2c/The lpi2c/

> +	 * signal resulting in an arbitration failure. This issue can be avoided
> +	 * by setting CLKHOLD.
> +	 *
> +	 * In order to ensure lpi2c function normally when the lpi2c speed is as
> +	 * low as 100kHz, CLKHOLD should be set 3 and it is also compatible with

/3/to 3/

> +	 * higher clock frequency like 400kHz and 1MHz.
> +	 */
> +	temp = SCFGR2_FILTSDA(2) | SCFGR2_FILTSCL(2) | SCFGR2_CLKHOLD(3);
> +	writel(temp, lpi2c_imx->base + LPI2C_SCFGR2);
> +
> +	/*
> +	 * Enable module:
> +	 * SCR_FILTEN can enable digital filter and output delay counter for LPI2C
> +	 * target mode. So SCR_FILTEN need be asserted when enable SDA/SCL FILTER
> +	 * and CLKHOLD.
> +	 */
> +	writel(SCR_SEN | SCR_FILTEN, lpi2c_imx->base + LPI2C_SCR);
> +
> +	/* Enable interrupt from i2c module */
> +	writel(SLAVE_INT_FLAG, lpi2c_imx->base + LPI2C_SIER);
> +}
> +
> +static int lpi2c_imx_reg_target(struct i2c_client *client)

lpi2c_imx_register_target as a name is a bit better, in my opinion

> +{
> +	struct lpi2c_imx_struct *lpi2c_imx = i2c_get_adapdata(client->adapter);
> +	int ret;
> +
> +	if (lpi2c_imx->target)
> +		return -EBUSY;
> +
> +	lpi2c_imx->target = client;
> +
> +	ret = pm_runtime_resume_and_get(lpi2c_imx->adapter.dev.parent);
> +	if (ret < 0) {
> +		dev_err(&lpi2c_imx->adapter.dev, "failed to resume i2c controller");
> +		return ret;
> +	}
> +
> +	lpi2c_imx_target_init(lpi2c_imx);
> +
> +	return 0;
> +}
> +
> +static int lpi2c_imx_unreg_target(struct i2c_client *client)

lpi2c_imx_unregister_target sounds better to me.

> +{
> +	struct lpi2c_imx_struct *lpi2c_imx = i2c_get_adapdata(client->adapter);
> +	int ret;
> +
> +	if (!lpi2c_imx->target)
> +		return -EINVAL;
> +

...

> +static int lpi2c_suspend_noirq(struct device *dev)
> +{
> +	int ret;
> +
> +	ret = pm_runtime_force_suspend(dev);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

This function can simply be:

   static int lpi2c_suspend_noirq(struct device *dev)
   {
   	return pm_runtime_force_suspend(dev);
   }

but I'm not strong for it, your choice.

> +}
> +
> +static int lpi2c_resume_noirq(struct device *dev)
> +{
> +	struct lpi2c_imx_struct *lpi2c_imx = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = pm_runtime_force_resume(dev);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * If i2c module powered down in system suspend, register
> +	 * value will lose. So reinit target when system resume.
> +	 */

Can we re-write this to something like:

	/*
	 * If the I2C module powers down during system suspend,
	 * the register values will be lost. Therefore, reinitialize
	 * the target when the system resumes.
	 */

Thanks,
Andi

> +	if (lpi2c_imx->target)
> +		lpi2c_imx_target_init(lpi2c_imx);
> +
> +	return 0;
> +}
> +
>  static const struct dev_pm_ops lpi2c_pm_ops = {
> -	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> -				      pm_runtime_force_resume)
> +	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(lpi2c_suspend_noirq,
> +				      lpi2c_resume_noirq)
>  	SET_RUNTIME_PM_OPS(lpi2c_runtime_suspend,
>  			   lpi2c_runtime_resume, NULL)
>  };
> -- 
> 2.34.1
> 




[Index of Archives]     [Linux GPIO]     [Linux SPI]     [Linux Hardward Monitoring]     [LM Sensors]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux