| From 865433df5d11aef7cfe5d51b362b6276bddb7a15 Mon Sep 17 00:00:00 2001 |
| From: Biwen Li <biwen.li@nxp.com> |
| Date: Fri, 2 Aug 2019 17:45:56 +0800 |
| Subject: [PATCH] i2c: imx: support slave mode for imx I2C driver |
| |
| The patch supports slave mode for imx I2C driver |
| |
| Reviewed-by: Clark Wang <xiaoning.wang@nxp.com> |
| Signed-off-by: Biwen Li <biwen.li@nxp.com> |
| --- |
| drivers/i2c/busses/i2c-imx.c | 219 +++++++++++++++++++++++++++++++++++++++---- |
| 1 file changed, 201 insertions(+), 18 deletions(-) |
| |
| --- a/drivers/i2c/busses/i2c-imx.c |
| +++ b/drivers/i2c/busses/i2c-imx.c |
| @@ -265,6 +265,9 @@ struct imx_i2c_struct { |
| int pmuxcr_set; |
| int pmuxcr_endian; |
| void __iomem *pmuxcr_addr; |
| +#if IS_ENABLED(CONFIG_I2C_SLAVE) |
| + struct i2c_client *slave; |
| +#endif |
| }; |
| |
| static const struct imx_i2c_hwdata imx1_i2c_hwdata = { |
| @@ -357,6 +360,14 @@ static inline unsigned char imx_i2c_read |
| return readb(i2c_imx->base + (reg << i2c_imx->hwdata->regshift)); |
| } |
| |
| +/* Set up i2c controller register and i2c status register to default value. */ |
| +static void i2c_imx_reset_regs(struct imx_i2c_struct *i2c_imx) |
| +{ |
| + imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN, |
| + i2c_imx, IMX_I2C_I2CR); |
| + imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR); |
| +} |
| + |
| /* Functions for DMA support */ |
| static void i2c_imx_dma_request(struct imx_i2c_struct *i2c_imx, |
| dma_addr_t phy_addr) |
| @@ -705,21 +716,33 @@ static void i2c_imx_stop(struct imx_i2c_ |
| imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); |
| } |
| |
| -static irqreturn_t i2c_imx_isr(int irq, void *dev_id) |
| +/* Clear interrupt flag bit */ |
| +static void i2c_imx_clr_if_bit(unsigned int status, struct imx_i2c_struct *i2c_imx) |
| { |
| - struct imx_i2c_struct *i2c_imx = dev_id; |
| - unsigned int temp; |
| + status &= ~I2SR_IIF; |
| + status |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IIF); |
| + imx_i2c_write_reg(status, i2c_imx, IMX_I2C_I2SR); |
| +} |
| |
| - temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); |
| - if (temp & I2SR_IIF) { |
| - /* save status register */ |
| - i2c_imx->i2csr = temp; |
| - i2c_imx_clear_irq(i2c_imx, I2SR_IIF); |
| - wake_up(&i2c_imx->queue); |
| - return IRQ_HANDLED; |
| - } |
| +/* Clear arbitration lost bit */ |
| +static void i2c_imx_clr_al_bit(unsigned int status, struct imx_i2c_struct *i2c_imx) |
| +{ |
| + status &= ~I2SR_IAL; |
| + status |= (i2c_imx->hwdata->i2sr_clr_opcode & I2SR_IAL); |
| + imx_i2c_write_reg(status, i2c_imx, IMX_I2C_I2SR); |
| +} |
| + |
| +static irqreturn_t i2c_imx_master_isr(struct imx_i2c_struct *i2c_imx) |
| +{ |
| + unsigned int status; |
| + |
| + /* Save status register */ |
| + status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); |
| + i2c_imx->i2csr = status | I2SR_IIF; |
| + |
| + wake_up(&i2c_imx->queue); |
| |
| - return IRQ_NONE; |
| + return IRQ_HANDLED; |
| } |
| |
| static int i2c_imx_dma_write(struct imx_i2c_struct *i2c_imx, |
| @@ -1094,6 +1117,13 @@ static int i2c_imx_xfer(struct i2c_adapt |
| |
| dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); |
| |
| +#if IS_ENABLED(CONFIG_I2C_SLAVE) |
| + if (i2c_imx->slave) { |
| + dev_err(&i2c_imx->adapter.dev, "Please not do operations of master mode in slave mode"); |
| + return -EBUSY; |
| + } |
| +#endif |
| + |
| if (!pm_runtime_enabled(i2c_imx->adapter.dev.parent)) { |
| pm_runtime_enable(i2c_imx->adapter.dev.parent); |
| enable_runtime_pm = true; |
| @@ -1307,11 +1337,169 @@ static u32 i2c_imx_func(struct i2c_adapt |
| | I2C_FUNC_SMBUS_READ_BLOCK_DATA; |
| } |
| |
| +#if IS_ENABLED(CONFIG_I2C_SLAVE) |
| +static int i2c_imx_slave_init(struct imx_i2c_struct *i2c_imx) |
| +{ |
| + int temp; |
| + |
| + /* Resume */ |
| + temp = pm_runtime_get_sync(i2c_imx->adapter.dev.parent); |
| + if (temp < 0) { |
| + dev_err(&i2c_imx->adapter.dev, "failed to resume i2c controller"); |
| + return temp; |
| + } |
| + |
| + /* Set slave addr. */ |
| + imx_i2c_write_reg((i2c_imx->slave->addr << 1), i2c_imx, IMX_I2C_IADR); |
| + |
| + i2c_imx_reset_regs(i2c_imx); |
| + |
| + /* Enable module */ |
| + temp = i2c_imx->hwdata->i2cr_ien_opcode; |
| + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); |
| + |
| + /* Enable interrupt from i2c module */ |
| + temp |= I2CR_IIEN; |
| + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); |
| + |
| + /* Wait controller to be stable */ |
| + usleep_range(50, 150); |
| + return 0; |
| +} |
| + |
| +static irqreturn_t i2c_imx_slave_isr(struct imx_i2c_struct *i2c_imx) |
| +{ |
| + unsigned int status, ctl; |
| + u8 value; |
| + |
| + if (!i2c_imx->slave) { |
| + dev_err(&i2c_imx->adapter.dev, "cannot deal with slave irq,i2c_imx->slave is null"); |
| + return IRQ_NONE; |
| + } |
| + |
| + status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); |
| + ctl = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); |
| + if (status & I2SR_IAL) { /* Arbitration lost */ |
| + i2c_imx_clr_al_bit(status, i2c_imx); |
| + } else if (status & I2SR_IAAS) { /* Addressed as a slave */ |
| + if (status & I2SR_SRW) { /* Master wants to read from us*/ |
| + dev_dbg(&i2c_imx->adapter.dev, "read requested"); |
| + i2c_slave_event(i2c_imx->slave, I2C_SLAVE_READ_REQUESTED, &value); |
| + |
| + /* Slave transmit */ |
| + ctl |= I2CR_MTX; |
| + imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR); |
| + |
| + /* Send data */ |
| + imx_i2c_write_reg(value, i2c_imx, IMX_I2C_I2DR); |
| + } else { /* Master wants to write to us */ |
| + dev_dbg(&i2c_imx->adapter.dev, "write requested"); |
| + i2c_slave_event(i2c_imx->slave, I2C_SLAVE_WRITE_REQUESTED, &value); |
| + |
| + /* Slave receive */ |
| + ctl &= ~I2CR_MTX; |
| + imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR); |
| + /* Dummy read */ |
| + imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); |
| + } |
| + } else if (!(ctl & I2CR_MTX)) { /* Receive mode */ |
| + if (status & I2SR_IBB) { /* No STOP signal detected */ |
| + ctl &= ~I2CR_MTX; |
| + imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR); |
| + |
| + value = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); |
| + i2c_slave_event(i2c_imx->slave, I2C_SLAVE_WRITE_RECEIVED, &value); |
| + } else { /* STOP signal is detected */ |
| + dev_dbg(&i2c_imx->adapter.dev, |
| + "STOP signal detected"); |
| + i2c_slave_event(i2c_imx->slave, I2C_SLAVE_STOP, &value); |
| + } |
| + } else if (!(status & I2SR_RXAK)) { /* Transmit mode received ACK */ |
| + ctl |= I2CR_MTX; |
| + imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR); |
| + |
| + i2c_slave_event(i2c_imx->slave, I2C_SLAVE_READ_PROCESSED, &value); |
| + |
| + imx_i2c_write_reg(value, i2c_imx, IMX_I2C_I2DR); |
| + } else { /* Transmit mode received NAK */ |
| + ctl &= ~I2CR_MTX; |
| + imx_i2c_write_reg(ctl, i2c_imx, IMX_I2C_I2CR); |
| + imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); |
| + } |
| + return IRQ_HANDLED; |
| +} |
| + |
| +static int i2c_imx_reg_slave(struct i2c_client *client) |
| +{ |
| + struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(client->adapter); |
| + int ret; |
| + if (i2c_imx->slave) |
| + return -EBUSY; |
| + |
| + i2c_imx->slave = client; |
| + |
| + ret = i2c_imx_slave_init(i2c_imx); |
| + if (ret < 0) |
| + dev_err(&i2c_imx->adapter.dev, "failed to switch to slave mode"); |
| + |
| + return ret; |
| +} |
| + |
| +static int i2c_imx_unreg_slave(struct i2c_client *client) |
| +{ |
| + struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(client->adapter); |
| + int ret; |
| + |
| + if (!i2c_imx->slave) |
| + return -EINVAL; |
| + |
| + /* Reset slave address. */ |
| + imx_i2c_write_reg(0, i2c_imx, IMX_I2C_IADR); |
| + |
| + i2c_imx_reset_regs(i2c_imx); |
| + |
| + i2c_imx->slave = NULL; |
| + |
| + /* Suspend */ |
| + ret = pm_runtime_put_sync(i2c_imx->adapter.dev.parent); |
| + if (ret < 0) |
| + dev_err(&i2c_imx->adapter.dev, "failed to suspend i2c controller"); |
| + |
| + return ret; |
| +} |
| +#endif |
| + |
| static const struct i2c_algorithm i2c_imx_algo = { |
| .master_xfer = i2c_imx_xfer, |
| .functionality = i2c_imx_func, |
| +#if IS_ENABLED(CONFIG_I2C_SLAVE) |
| + .reg_slave = i2c_imx_reg_slave, |
| + .unreg_slave = i2c_imx_unreg_slave, |
| +#endif |
| }; |
| |
| +static irqreturn_t i2c_imx_isr(int irq, void *dev_id) |
| +{ |
| + struct imx_i2c_struct *i2c_imx = dev_id; |
| + unsigned int status, ctl; |
| + irqreturn_t irq_status = IRQ_NONE; |
| + |
| + status = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); |
| + ctl = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); |
| + |
| + if (status & I2SR_IIF) { |
| + i2c_imx_clr_if_bit(status, i2c_imx); |
| + if (ctl & I2CR_MSTA) |
| + irq_status = i2c_imx_master_isr(i2c_imx); |
| +#if IS_ENABLED(CONFIG_I2C_SLAVE) |
| + else |
| + irq_status = i2c_imx_slave_isr(i2c_imx); |
| +#endif |
| + } |
| + |
| + return irq_status; |
| +} |
| + |
| static int i2c_imx_probe(struct platform_device *pdev) |
| { |
| struct imx_i2c_struct *i2c_imx; |
| @@ -1420,10 +1608,7 @@ static int i2c_imx_probe(struct platform |
| if (is_imx7d_i2c(i2c_imx) && i2c_imx->bitrate > IMX_I2C_MAX_E_BIT_RATE) |
| i2c_imx->bitrate = IMX_I2C_MAX_E_BIT_RATE; |
| |
| - /* Set up chip registers to defaults */ |
| - imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN, |
| - i2c_imx, IMX_I2C_I2CR); |
| - imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR); |
| + i2c_imx_reset_regs(i2c_imx); |
| |
| /* Init optional bus recovery */ |
| if (of_match_node(pinmux_of_match, pdev->dev.of_node)) |