Message ID | 20240912082413.435267-1-carlos.song@nxp.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | [V4] i2c: imx-lpi2c: add target mode support | expand |
Hi Carlos, On Thu, Sep 12, 2024 at 04:24:13PM GMT, carlos.song@nxp.com wrote: > From: Carlos Song <carlos.song@nxp.com> > > 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@nxp.com> ... > +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 >
> Hi Carlos, > > On Thu, Sep 12, 2024 at 04:24:13PM GMT, carlos.song@nxp.com wrote: > > From: Carlos Song <carlos.song@nxp.com> > > > > 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@nxp.com> > > ... > > > +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. > Hi, Andi, Thanks for your suggestion. In fact, if lpi2c_imx->target is assigned, master still can generate the interrupt and will also can enter master's irq handler. Because LPI2C has independent slave register group and master register group. SCR register is slave control register, SSR register is slave status register and SIER register is slave interrupt enable register. So Even if target is assigned, and if the interrupt is triggered by master, this judgment will NOT hit: if ((scr & SCR_SEN) && sier_filter) return lpi2c_imx_target_isr(lpi2c_imx, ssr, sier_filter); so logic will keep going then enter the lpi2c_imx_master_isr(). Over all, when lpi2c_imx->target is assigned, either enter the master handler or the target handler. When lpi2c_imx->target is not assigned, only enter the master handler. Maybe my commit log "master mode and target mode can work at the same time" is misleading. I will change to "Both master mode and target mode can be enabled". Do I understand you correctly? If I do. Please tell me I will send V5 patch for other fixes. Carlos > > + } > > + > > + /* 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 > >
diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index f2bbd9898551..5e4157c47104 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 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 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 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,56 @@ 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 +1078,118 @@ 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; + + 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 */ + if ((scr & SCR_SEN) && sier_filter) + return lpi2c_imx_target_isr(lpi2c_imx, ssr, sier_filter); + } + + /* Otherwise triggered by master then handle irq in master 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 */ + 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 +1265,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 +1417,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) };