[PATCH V3] i2c: imx-lpi2c: add target mode support
Carlos Song
carlos.song at nxp.com
Wed Sep 11 08:07:42 PDT 2024
> LPI2C support master controller and target controller enabled simultaneously.
> Both controllers share same SDA/SCL lines and interrupt source but has
> separate control and status registers.
> Even if target mode is enabled, LPI2C can still work normally as master
> controller at the same time.
>
> 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 client. For simple use and test, Linux
> I2C slave EEPROM backend can be used.
>
Hi, Andi
Just now I found I still have work to do! Before I notice to need to enrich commit log only.
Oh..It's a little embarrassing. Sorry for missing other comment. I will send V4 then finish the rest:).
> Signed-off-by: Carlos Song <carlos.song at nxp.com>
> ---
> Change for V3:
> - According to Andi's suggestion, enrich this patch commit log.
> No code change.
> Change for V2:
> - remove unused variable 'lpi2c_imx' in lpi2c_suspend_noirq.
> ---
> drivers/i2c/busses/i2c-imx-lpi2c.c | 252 ++++++++++++++++++++++++++++-
> 1 file changed, 248 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c
> b/drivers/i2c/busses/i2c-imx-lpi2c.c
> index f2bbd9898551..2d68faf6847e 100644
> --- a/drivers/i2c/busses/i2c-imx-lpi2c.c
> +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c
> @@ -43,6 +43,20 @@
> #define LPI2C_MTDR 0x60 /* i2c master TX data register */
> #define LPI2C_MRDR 0x70 /* i2c master RX data register */
>
> +#define LPI2C_SCR 0x110 /* i2c target contrl register */
> +#define LPI2C_SSR 0x114 /* i2c target status register */
> +#define LPI2C_SIER 0x118 /* i2c target interrupt enable */
> +#define LPI2C_SDER 0x11C /* i2c target DMA enable */
> +#define LPI2C_SCFGR0 0x120 /* i2c target configuration */
> +#define LPI2C_SCFGR1 0x124 /* i2c target configuration */
> +#define LPI2C_SCFGR2 0x128 /* i2c target configuration */
> +#define LPI2C_SAMR 0x140 /* i2c target address match */
> +#define LPI2C_SASR 0x150 /* i2c target address status */
> +#define LPI2C_STAR 0x154 /* i2c target transmit ACK */
> +#define LPI2C_STDR 0x160 /* i2c target transmit data */
> +#define LPI2C_SRDR 0x170 /* i2c target receive data */
> +#define LPI2C_SRDROR 0x178 /* i2c target receive data read only */
> +
> /* i2c command */
> #define TRAN_DATA 0X00
> #define RECV_DATA 0X01
> @@ -76,6 +90,42 @@
> #define MDER_TDDE BIT(0)
> #define MDER_RDDE BIT(1)
>
> +#define SCR_SEN BIT(0)
> +#define SCR_RST BIT(1)
> +#define SCR_FILTEN BIT(4)
> +#define SCR_RTF BIT(8)
> +#define SCR_RRF BIT(9)
> +#define SCFGR1_RXSTALL BIT(1)
> +#define SCFGR1_TXDSTALL BIT(2)
> +#define SCFGR2_FILTSDA_SHIFT 24
> +#define SCFGR2_FILTSCL_SHIFT 16
> +#define SCFGR2_CLKHOLD(x) (x)
> +#define SCFGR2_FILTSDA(x) ((x) << SCFGR2_FILTSDA_SHIFT)
> +#define SCFGR2_FILTSCL(x) ((x) << SCFGR2_FILTSCL_SHIFT)
> +#define SSR_TDF BIT(0)
> +#define SSR_RDF BIT(1)
> +#define SSR_AVF BIT(2)
> +#define SSR_TAF BIT(3)
> +#define SSR_RSF BIT(8)
> +#define SSR_SDF BIT(9)
> +#define SSR_BEF BIT(10)
> +#define SSR_FEF BIT(11)
> +#define SSR_SBF BIT(24)
> +#define SSR_BBF BIT(25)
> +#define SSR_CLEAR_BITS (SSR_RSF | SSR_SDF | SSR_BEF | SSR_FEF)
> +#define SIER_TDIE BIT(0)
> +#define SIER_RDIE BIT(1)
> +#define SIER_AVIE BIT(2)
> +#define SIER_TAIE BIT(3)
> +#define SIER_RSIE BIT(8)
> +#define SIER_SDIE BIT(9)
> +#define SIER_BEIE BIT(10)
> +#define SIER_FEIE BIT(11)
> +#define SIER_AM0F BIT(12)
> +#define SASR_READ_REQ 0x1
> +#define SLAVE_INT_FLAG (SIER_TDIE | SIER_RDIE | SIER_AVIE | \
> + SIER_SDIE | SIER_BEIE)
> +
> #define I2C_CLK_RATIO 2
> #define CHUNK_DATA 256
>
> @@ -134,6 +184,7 @@ struct lpi2c_imx_struct {
> struct i2c_bus_recovery_info rinfo;
> bool can_use_dma;
> struct lpi2c_imx_dma *dma;
> + struct i2c_client *target;
> };
>
> static void lpi2c_imx_intctrl(struct lpi2c_imx_struct *lpi2c_imx, @@ -958,9
> +1009,57 @@ static int lpi2c_imx_xfer(struct i2c_adapter *adapter,
> return (result < 0) ? result : num;
> }
>
> -static irqreturn_t lpi2c_imx_isr(int irq, void *dev_id)
> +static irqreturn_t lpi2c_imx_target_isr(struct lpi2c_imx_struct *lpi2c_imx,
> + u32 ssr, u32 sier_filter)
> +{
> + u8 value;
> + u32 sasr;
> +
> + /* Arbitration lost */
> + if (sier_filter & SSR_BEF) {
> + writel(0, lpi2c_imx->base + LPI2C_SIER);
> + return IRQ_HANDLED;
> + }
> +
> + /* Address detected */
> + if (sier_filter & SSR_AVF) {
> + sasr = readl(lpi2c_imx->base + LPI2C_SASR);
> + if (SASR_READ_REQ & sasr) {
> + /* Read request */
> + i2c_slave_event(lpi2c_imx->target,
> I2C_SLAVE_READ_REQUESTED, &value);
> + writel(value, lpi2c_imx->base + LPI2C_STDR);
> + goto ret;
> + } else {
> + /* Write request */
> + i2c_slave_event(lpi2c_imx->target,
> I2C_SLAVE_WRITE_REQUESTED, &value);
> + }
> + }
> +
> + if (sier_filter & SSR_SDF) {
> + /* STOP */
> + i2c_slave_event(lpi2c_imx->target, I2C_SLAVE_STOP, &value);
> + }
> +
> + if (sier_filter & SSR_TDF) {
> + /* Target send data */
> + i2c_slave_event(lpi2c_imx->target, I2C_SLAVE_READ_PROCESSED,
> &value);
> + writel(value, lpi2c_imx->base + LPI2C_STDR);
> + }
> +
> + if (sier_filter & SSR_RDF) {
> + /* Target receive data */
> + value = readl(lpi2c_imx->base + LPI2C_SRDR);
> + i2c_slave_event(lpi2c_imx->target, I2C_SLAVE_WRITE_RECEIVED,
> &value);
> + }
> +
> +ret:
> + /* Clear SSR */
> + writel(ssr & SSR_CLEAR_BITS, lpi2c_imx->base + LPI2C_SSR);
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t lpi2c_imx_master_isr(struct lpi2c_imx_struct
> +*lpi2c_imx)
> {
> - struct lpi2c_imx_struct *lpi2c_imx = dev_id;
> unsigned int enabled;
> unsigned int temp;
>
> @@ -980,6 +1079,119 @@ static irqreturn_t lpi2c_imx_isr(int irq, void
> *dev_id)
> return IRQ_HANDLED;
> }
>
> +static irqreturn_t lpi2c_imx_isr(int irq, void *dev_id) {
> + struct lpi2c_imx_struct *lpi2c_imx = dev_id;
> + u32 ssr, sier_filter;
> + unsigned int scr;
> +
> + if (lpi2c_imx->target) {
> + scr = readl(lpi2c_imx->base + LPI2C_SCR);
> + ssr = readl(lpi2c_imx->base + LPI2C_SSR);
> + sier_filter = ssr & readl(lpi2c_imx->base + LPI2C_SIER);
> + if ((scr & SCR_SEN) && sier_filter)
> + return lpi2c_imx_target_isr(lpi2c_imx, ssr, sier_filter);
> + else
> + return lpi2c_imx_master_isr(lpi2c_imx);
> + } else {
> + return lpi2c_imx_master_isr(lpi2c_imx);
> + }
> +}
> +
> +static void lpi2c_imx_target_init(struct lpi2c_imx_struct *lpi2c_imx) {
> + int temp;
> +
> + /* reset target module */
> + writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR);
> + writel(0, lpi2c_imx->base + LPI2C_SCR);
> +
> + /* Set target addr */
> + 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
> + * releasing SCL signal. Lpi2c master will mistakenly think it is a stop
> + * 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
> + * 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) {
> + 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) {
> + struct lpi2c_imx_struct *lpi2c_imx = i2c_get_adapdata(client->adapter);
> + int ret;
> +
> + if (!lpi2c_imx->target)
> + return -EINVAL;
> +
> + /* Reset target address. */
> + writel(0, lpi2c_imx->base + LPI2C_SAMR);
> +
> + writel(SCR_RST, lpi2c_imx->base + LPI2C_SCR);
> + writel(0, lpi2c_imx->base + LPI2C_SCR);
> +
> + lpi2c_imx->target = NULL;
> +
> + ret = pm_runtime_put_sync(lpi2c_imx->adapter.dev.parent);
> + if (ret < 0)
> + dev_err(&lpi2c_imx->adapter.dev, "failed to suspend i2c controller");
> +
> + return ret;
> +}
> +
> static int lpi2c_imx_init_recovery_info(struct lpi2c_imx_struct *lpi2c_imx,
> struct platform_device *pdev)
> {
> @@ -1055,6 +1267,8 @@ static u32 lpi2c_imx_func(struct i2c_adapter
> *adapter) static const struct i2c_algorithm lpi2c_imx_algo = {
> .master_xfer = lpi2c_imx_xfer,
> .functionality = lpi2c_imx_func,
> + .reg_slave = lpi2c_imx_reg_target,
> + .unreg_slave = lpi2c_imx_unreg_target,
> };
>
> static const struct of_device_id lpi2c_imx_of_match[] = { @@ -1205,9
> +1419,39 @@ static int __maybe_unused lpi2c_runtime_resume(struct device
> *dev)
> return 0;
> }
>
> +static int lpi2c_suspend_noirq(struct device *dev) {
> + int ret;
> +
> + ret = pm_runtime_force_suspend(dev);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +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.
> + */
> + 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
More information about the linux-arm-kernel
mailing list