From patchwork Mon Aug 13 21:16:12 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Lengfeld X-Patchwork-Id: 10564815 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id CC0961815 for ; Mon, 13 Aug 2018 21:16:26 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id BCC07286FD for ; Mon, 13 Aug 2018 21:16:26 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B11F428B68; Mon, 13 Aug 2018 21:16:26 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E449C2946D for ; Mon, 13 Aug 2018 21:16:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730131AbeHNAAU (ORCPT ); Mon, 13 Aug 2018 20:00:20 -0400 Received: from stcim.de ([78.46.90.227]:34450 "EHLO stcim.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729815AbeHNAAT (ORCPT ); Mon, 13 Aug 2018 20:00:19 -0400 Received: from xdsl-87-78-37-34.netcologne.de ([87.78.37.34] helo=localhost.localdomain) by stcim with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.89) (envelope-from ) id 1fpKCG-0008Ap-5a; Mon, 13 Aug 2018 23:16:16 +0200 From: Stefan Lengfeld To: linux-i2c@vger.kernel.org Cc: wsa@the-dreams.de, preid@electromag.com.au, j-keerthy@ti.com, tony@atomide.com, nsekhar@ti.com, vigneshr@ti.com, linux-omap@vger.kernel.org Subject: [RFC PATCH 2/4] i2c: imx: implement IRQ less master_xfer function Date: Mon, 13 Aug 2018 23:16:12 +0200 Message-Id: <20180813211614.16440-3-contact@stefanchrist.eu> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20180813211614.16440-1-contact@stefanchrist.eu> References: <5bb4b898-acc3-7c73-2285-cf84a88961e6@ti.com> <20180813211614.16440-1-contact@stefanchrist.eu> Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Rework the read and write code paths in the driver to support operation in IRQ disabled contexts. The patch is currently tested only on a phyCORE-i.MX6 Solo board. The driver supports normal operation, DMA transfers and now the polling mode or also called sleep-free or IRQ-less operation. It makes the code not simpler or easier to read, but IRQ less I2C transfers are needed on some hardware configurations, e.g. to trigger a reboot in an external PMIC chip. TODOs: - checkpatch complains that in_atomic() should not be used in driver code. Signed-off-by: Stefan Lengfeld --- drivers/i2c/busses/i2c-imx.c | 113 +++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index 498c5e891649..8a17c8c454c7 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -404,7 +404,7 @@ static void i2c_imx_dma_free(struct imx_i2c_struct *i2c_imx) dma->chan_using = NULL; } -static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy) +static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy, bool polling) { unsigned long orig_jiffies = jiffies; unsigned int temp; @@ -430,15 +430,39 @@ static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy) "<%s> I2C bus is busy\n", __func__); return -ETIMEDOUT; } - schedule(); + if (!polling) + schedule(); + else + udelay(100); } return 0; } -static int i2c_imx_trx_complete(struct imx_i2c_struct *i2c_imx) +static int i2c_imx_trx_complete(struct imx_i2c_struct *i2c_imx, bool polling) { - wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10); + if (!polling) { + wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10); + } else { + int counter = 0; + + while (1) { + unsigned int reg; + + reg = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR); + i2c_imx->i2csr = reg; + if (reg & I2SR_IIF) + break; + + if (counter > 1000) { + dev_err(&i2c_imx->adapter.dev, "<%s> TXR timeout\n", __func__); + return -EIO; + } + udelay(100); + counter++; + } + imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR); + } if (unlikely(!(i2c_imx->i2csr & I2SR_IIF))) { dev_dbg(&i2c_imx->adapter.dev, "<%s> Timeout\n", __func__); @@ -516,7 +540,7 @@ static int i2c_imx_clk_notifier_call(struct notifier_block *nb, return NOTIFY_OK; } -static int i2c_imx_start(struct imx_i2c_struct *i2c_imx) +static int i2c_imx_start(struct imx_i2c_struct *i2c_imx, bool polling) { unsigned int temp = 0; int result; @@ -529,24 +553,33 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx) imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx, IMX_I2C_I2CR); /* Wait controller to be stable */ - usleep_range(50, 150); + if (!polling) + usleep_range(50, 150); + else + udelay(50); /* Start I2C transaction */ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); temp |= I2CR_MSTA; imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); - result = i2c_imx_bus_busy(i2c_imx, 1); + result = i2c_imx_bus_busy(i2c_imx, 1, polling); if (result) return result; i2c_imx->stopped = 0; - temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK; + if (!polling) { + temp |= I2CR_IIEN | I2CR_MTX | I2CR_TXAK; + } else { + temp |= I2CR_MTX | I2CR_TXAK; + temp &= ~I2CR_IIEN; /* Disable interrupt */ + } + temp &= ~I2CR_DMAEN; imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); return result; } -static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx) +static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx, bool polling) { unsigned int temp = 0; @@ -568,7 +601,7 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx) } if (!i2c_imx->stopped) { - i2c_imx_bus_busy(i2c_imx, 0); + i2c_imx_bus_busy(i2c_imx, 0, polling); i2c_imx->stopped = 1; } @@ -651,7 +684,7 @@ static int i2c_imx_dma_write(struct imx_i2c_struct *i2c_imx, /* The last data byte must be transferred by the CPU. */ imx_i2c_write_reg(msgs->buf[msgs->len-1], i2c_imx, IMX_I2C_I2DR); - result = i2c_imx_trx_complete(i2c_imx); + result = i2c_imx_trx_complete(i2c_imx, false); if (result) return result; @@ -713,7 +746,7 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx, msgs->buf[msgs->len-2] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* read n byte data */ - result = i2c_imx_trx_complete(i2c_imx); + result = i2c_imx_trx_complete(i2c_imx, false); if (result) return result; @@ -726,7 +759,7 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx, temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); temp &= ~(I2CR_MSTA | I2CR_MTX); imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); - i2c_imx_bus_busy(i2c_imx, 0); + i2c_imx_bus_busy(i2c_imx, 0, false); i2c_imx->stopped = 1; } else { /* @@ -745,7 +778,7 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx, return 0; } -static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs) +static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bool polling) { int i, result; @@ -754,7 +787,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs) /* write slave address */ imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR); - result = i2c_imx_trx_complete(i2c_imx); + result = i2c_imx_trx_complete(i2c_imx, polling); if (result) return result; result = i2c_imx_acked(i2c_imx); @@ -768,7 +801,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs) "<%s> write byte: B%d=0x%X\n", __func__, i, msgs->buf[i]); imx_i2c_write_reg(msgs->buf[i], i2c_imx, IMX_I2C_I2DR); - result = i2c_imx_trx_complete(i2c_imx); + result = i2c_imx_trx_complete(i2c_imx, polling); if (result) return result; result = i2c_imx_acked(i2c_imx); @@ -778,7 +811,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs) return 0; } -static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bool is_lastmsg) +static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bool is_lastmsg, bool polling) { int i, result; unsigned int temp; @@ -790,7 +823,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo /* write slave address */ imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR); - result = i2c_imx_trx_complete(i2c_imx); + result = i2c_imx_trx_complete(i2c_imx, polling); if (result) return result; result = i2c_imx_acked(i2c_imx); @@ -814,14 +847,14 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo dev_dbg(&i2c_imx->adapter.dev, "<%s> read data\n", __func__); - if (i2c_imx->dma && msgs->len >= DMA_THRESHOLD && !block_data) + if (i2c_imx->dma && msgs->len >= DMA_THRESHOLD && !block_data && !polling) return i2c_imx_dma_read(i2c_imx, msgs, is_lastmsg); /* read data */ for (i = 0; i < msgs->len; i++) { u8 len = 0; - result = i2c_imx_trx_complete(i2c_imx); + result = i2c_imx_trx_complete(i2c_imx, polling); if (result) return result; /* @@ -849,7 +882,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); temp &= ~(I2CR_MSTA | I2CR_MTX); imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); - i2c_imx_bus_busy(i2c_imx, 0); + i2c_imx_bus_busy(i2c_imx, 0, polling); i2c_imx->stopped = 1; } else { /* @@ -888,6 +921,7 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, int result; bool is_lastmsg = false; struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter); + bool polling = in_atomic() || irqs_disabled(); dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); @@ -896,11 +930,11 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, goto out; /* Start I2C transfer */ - result = i2c_imx_start(i2c_imx); + result = i2c_imx_start(i2c_imx, polling); if (result) { if (i2c_imx->adapter.bus_recovery_info) { i2c_recover_bus(&i2c_imx->adapter); - result = i2c_imx_start(i2c_imx); + result = i2c_imx_start(i2c_imx, polling); } } @@ -918,7 +952,7 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); temp |= I2CR_RSTA; imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); - result = i2c_imx_bus_busy(i2c_imx, 1); + result = i2c_imx_bus_busy(i2c_imx, 1, polling); if (result) goto fail0; } @@ -942,13 +976,17 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, (temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0), (temp & I2SR_RXAK ? 1 : 0)); #endif - if (msgs[i].flags & I2C_M_RD) - result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg); - else { - if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD) - result = i2c_imx_dma_write(i2c_imx, &msgs[i]); - else - result = i2c_imx_write(i2c_imx, &msgs[i]); + if (msgs[i].flags & I2C_M_RD) { + result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, polling); + } else { + if (!polling) { + if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD) + result = i2c_imx_dma_write(i2c_imx, &msgs[i]); + else + result = i2c_imx_write(i2c_imx, &msgs[i], polling); + } else { + result = i2c_imx_write(i2c_imx, &msgs[i], polling); + } } if (result) goto fail0; @@ -956,7 +994,7 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, fail0: /* Stop I2C transfer */ - i2c_imx_stop(i2c_imx); + i2c_imx_stop(i2c_imx, polling); pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent); pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent); @@ -1088,6 +1126,9 @@ static int i2c_imx_probe(struct platform_device *pdev) i2c_imx->adapter.dev.of_node = pdev->dev.of_node; i2c_imx->base = base; + /* Drivers supports IRQ less operations */ + i2c_adapter_irq_safe(&i2c_imx->adapter); + /* Get I2C clock */ i2c_imx->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(i2c_imx->clk)) { @@ -1118,6 +1159,14 @@ static int i2c_imx_probe(struct platform_device *pdev) /* Set up platform driver data */ platform_set_drvdata(pdev, i2c_imx); + /* + * Driver's PM callbacks are safe to be called in IRQ disabled + * contexts. Providing this information to the PM subsystem is required + * for the 'master_xfer' implementation that calls PM routines in IRQ + * disabled/atomic contexts, too. + */ + pm_runtime_irq_safe(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_active(&pdev->dev);