From patchwork Tue Jul 17 12:57:29 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vinod Koul X-Patchwork-Id: 142143 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp3625610ljj; Tue, 17 Jul 2018 05:58:21 -0700 (PDT) X-Google-Smtp-Source: AAOMgpc7cAWZdqKKlbjYQCxvIDHXGR6TgX8eMDUgBtdwEpeesnGMP6WlUbIpKOHm0w90RVL6vTlE X-Received: by 2002:a17:902:8f82:: with SMTP id z2-v6mr1528317plo.203.1531832301682; Tue, 17 Jul 2018 05:58:21 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531832301; cv=none; d=google.com; s=arc-20160816; b=QbgUxUI65gPNAZSHFjvvem3f4UhaA7YxB6PIdGx04LbBYElVsLBRsK+8iMwBO2qpwI ajVpBSXANWF/9i8IEL5VgVzqA/fZ258iJZtCypKiRKgdIvqR92+U98DfpQ6fBQz4CL/y vcK/dI3gaJdy2th8OvIRDe6jsCyIQIiS+DH8PNlGw3fa9klZXdjuDmIe30ySepOblowR jmSq5hYQcKc/3rVh6UaqyD3ayrt5liaYTXDkrHdhge30fCOQcn3Heke6XHVbwO8wPXHv ZXG3+nntar48AGQ26t4qv5bEbdJKr//LIOcT434lVBnjXMJ9hQd79cVBriw4Gd2my5Hd rGkw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=pp8fkJvB4E0AzjIbsXwvRWPp49hjr+x4HflLYv3Xtn8=; b=MKl1V5J89wb+V9Rn581/UeJR3y/PS2J8Ms0TDc2y3yz1hoylbIWVsFqBtPVGXmC2sd r9/UX0KkRYwPQPXvWaieUIRAimzKFkxnomdacwRCeNfAsbZykn+DIcXCTPRgJj2bYU8U bU2zJOL60btIpH3eXE04qyU4ac5qNmqDGVR6pvfbe0Ca2NOe80LRgPP4Vz+oGKGg31H1 TxQjCrrZdy/X3fdRS/HBs+j1Rz9XAZqTTS4PtPQuaTiAJGg1NOVJbdAKiQGXjNv+vlIS hOtMHEilTyesw/hnVmUEB6L7E2Xq4oato0oNcjs4y4vheyFmN+ytNh17+b04HXBrXb3/ y9pQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@kernel.org header.s=default header.b=X2AJsT9O; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id b18-v6si839068pgd.185.2018.07.17.05.58.21; Tue, 17 Jul 2018 05:58:21 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@kernel.org header.s=default header.b=X2AJsT9O; spf=pass (google.com: best guess record for domain of devicetree-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=devicetree-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1731535AbeGQNax (ORCPT + 5 others); Tue, 17 Jul 2018 09:30:53 -0400 Received: from mail.kernel.org ([198.145.29.99]:48700 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731242AbeGQNax (ORCPT ); Tue, 17 Jul 2018 09:30:53 -0400 Received: from localhost.localdomain (unknown [122.178.206.78]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPSA id EDDE120C0A; Tue, 17 Jul 2018 12:58:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1531832298; bh=CcffA3yGWHWmoqTURmzBZg0SE5GITD0iX2q8/CSW5NY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=X2AJsT9OIjFtyadcgNhPwsJ2KLp9E7owfXvtQE415L9oqQWeRPUw+JCk40cvM5gI4 0hbJjiMARKmIaPvxV8hGpxx4eZsIBhfs95+xT3jh9ejQwxaVfaa8GMY/nKD52XE0XW RM5r8/ju0oNFFq6M4ANRyOoiBZhZymGoB7tSuQ3M= From: Vinod Koul To: linux-i2c@vger.kernel.org Cc: Bjorn Andersson , linux-arm-msm@vger.kernel.org, Rob Herring , Wolfram Sang , devicetree@vger.kernel.org, Todor Tomov , Vinod Koul Subject: [PATCH v2 2/2] i2c: Add Qualcomm Camera Control Interface driver Date: Tue, 17 Jul 2018 18:27:29 +0530 Message-Id: <20180717125729.23045-3-vkoul@kernel.org> X-Mailer: git-send-email 2.14.4 In-Reply-To: <20180717125729.23045-1-vkoul@kernel.org> References: <20180717125729.23045-1-vkoul@kernel.org> Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org From: Todor Tomov This commit adds I2C bus support for the Camera Control Interface (CCI) controller found on the Qualcomm SoC processors. CCI versions supported: - 1.0.8 - found on MSM8916/APQ8016 - 1.4.0 - found on MSM8996/APQ8096 I2C bus. Signed-off-by: Todor Tomov Signed-off-by: Vinod Koul --- drivers/i2c/busses/Kconfig | 10 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-qcom-cci.c | 793 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 804 insertions(+) create mode 100644 drivers/i2c/busses/i2c-qcom-cci.c -- 2.14.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 4f8df2ec87b1..033958bcdd4b 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -828,6 +828,16 @@ config I2C_PXA_SLAVE is necessary for systems where the PXA may be a target on the I2C bus. +config I2C_QCOM_CCI + tristate "Qualcomm Camera Control Interface" + depends on ARCH_QCOM + help + If you say yes to this option, support will be included for the + built-in camera control interface on the Qualcomm SoCs. + + This driver can also be built as a module. If so, the module + will be called i2c-qcom-cci. + config I2C_QUP tristate "Qualcomm QUP based I2C controller" depends on ARCH_QCOM diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 5a869144a0c5..d6e48ec3372a 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_I2C_PNX) += i2c-pnx.o obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o obj-$(CONFIG_I2C_PXA) += i2c-pxa.o obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o +obj-$(CONFIG_I2C_QCOM_CCI) += i2c-qcom-cci.o obj-$(CONFIG_I2C_QUP) += i2c-qup.o obj-$(CONFIG_I2C_RIIC) += i2c-riic.o obj-$(CONFIG_I2C_RK3X) += i2c-rk3x.o diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c new file mode 100644 index 000000000000..972e3dcc4556 --- /dev/null +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -0,0 +1,793 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. +// Copyright (c) 2017-18 Linaro Limited. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CCI_HW_VERSION 0x0 +#define CCI_RESET_CMD 0x004 +#define CCI_RESET_CMD_MASK 0x0f73f3f7 +#define CCI_RESET_CMD_M0_MASK 0x000003f1 +#define CCI_RESET_CMD_M1_MASK 0x0003f001 +#define CCI_QUEUE_START 0x008 +#define CCI_HALT_REQ 0x034 +#define CCI_HALT_REQ_I2C_M0_Q0Q1 BIT(0) +#define CCI_HALT_REQ_I2C_M1_Q0Q1 BIT(1) + +#define CCI_I2C_Mm_SCL_CTL(m) (0x100 + 0x100 * (m)) +#define CCI_I2C_Mm_SDA_CTL_0(m) (0x104 + 0x100 * (m)) +#define CCI_I2C_Mm_SDA_CTL_1(m) (0x108 + 0x100 * (m)) +#define CCI_I2C_Mm_SDA_CTL_2(m) (0x10c + 0x100 * (m)) +#define CCI_I2C_Mm_MISC_CTL(m) (0x110 + 0x100 * (m)) + +#define CCI_I2C_Mm_READ_DATA(m) (0x118 + 0x100 * (m)) +#define CCI_I2C_Mm_READ_BUF_LEVEL(m) (0x11c + 0x100 * (m)) +#define CCI_I2C_Mm_Qn_EXEC_WORD_CNT(m, n) (0x300 + 0x200 * (m) + 0x100 * (n)) +#define CCI_I2C_Mm_Qn_CUR_WORD_CNT(m, n) (0x304 + 0x200 * (m) + 0x100 * (n)) +#define CCI_I2C_Mm_Qn_CUR_CMD(m, n) (0x308 + 0x200 * (m) + 0x100 * (n)) +#define CCI_I2C_Mm_Qn_REPORT_STATUS(m, n) (0x30c + 0x200 * (m) + 0x100 * (n)) +#define CCI_I2C_Mm_Qn_LOAD_DATA(m, n) (0x310 + 0x200 * (m) + 0x100 * (n)) + +#define CCI_IRQ_GLOBAL_CLEAR_CMD 0xc00 +#define CCI_IRQ_MASK_0 0xc04 +#define CCI_IRQ_MASK_0_I2C_M0_RD_DONE BIT(0) +#define CCI_IRQ_MASK_0_I2C_M0_Q0_REPORT BIT(4) +#define CCI_IRQ_MASK_0_I2C_M0_Q1_REPORT BIT(8) +#define CCI_IRQ_MASK_0_I2C_M1_RD_DONE BIT(12) +#define CCI_IRQ_MASK_0_I2C_M1_Q0_REPORT BIT(16) +#define CCI_IRQ_MASK_0_I2C_M1_Q1_REPORT BIT(20) +#define CCI_IRQ_MASK_0_RST_DONE_ACK BIT(24) +#define CCI_IRQ_MASK_0_I2C_M0_Q0Q1_HALT_ACK BIT(25) +#define CCI_IRQ_MASK_0_I2C_M1_Q0Q1_HALT_ACK BIT(26) +#define CCI_IRQ_MASK_0_I2C_M0_ERROR 0x18000ee6 +#define CCI_IRQ_MASK_0_I2C_M1_ERROR 0x60ee6000 +#define CCI_IRQ_CLEAR_0 0xc08 +#define CCI_IRQ_STATUS_0 0xc0c +#define CCI_IRQ_STATUS_0_I2C_M0_RD_DONE BIT(0) +#define CCI_IRQ_STATUS_0_I2C_M0_Q0_REPORT BIT(4) +#define CCI_IRQ_STATUS_0_I2C_M0_Q1_REPORT BIT(8) +#define CCI_IRQ_STATUS_0_I2C_M1_RD_DONE BIT(12) +#define CCI_IRQ_STATUS_0_I2C_M1_Q0_REPORT BIT(16) +#define CCI_IRQ_STATUS_0_I2C_M1_Q1_REPORT BIT(20) +#define CCI_IRQ_STATUS_0_RST_DONE_ACK BIT(24) +#define CCI_IRQ_STATUS_0_I2C_M0_Q0Q1_HALT_ACK BIT(25) +#define CCI_IRQ_STATUS_0_I2C_M1_Q0Q1_HALT_ACK BIT(26) +#define CCI_IRQ_STATUS_0_I2C_M0_ERROR 0x18000ee6 +#define CCI_IRQ_STATUS_0_I2C_M1_ERROR 0x60ee6000 + +#define CCI_TIMEOUT_MS 100 +#define NUM_MASTERS 2 +#define NUM_QUEUES 2 + +/* Max number of resources + 1 for a NULL terminator */ +#define CCI_RES_MAX 6 + +enum cci_i2c_cmd { + CCI_I2C_SET_PARAM = 1, + CCI_I2C_WAIT, + CCI_I2C_WAIT_SYNC, + CCI_I2C_WAIT_GPIO_EVENT, + CCI_I2C_TRIG_I2C_EVENT, + CCI_I2C_LOCK, + CCI_I2C_UNLOCK, + CCI_I2C_REPORT, + CCI_I2C_WRITE, + CCI_I2C_READ, + CCI_I2C_WRITE_DISABLE_P, + CCI_I2C_READ_DISABLE_P, +}; + +enum { + I2C_MODE_STANDARD, + I2C_MODE_FAST, + I2C_MODE_FAST_PLUS, +}; + +enum cci_i2c_queue_t { + QUEUE_0, + QUEUE_1 +}; + +struct cci_res { + char *clock[CCI_RES_MAX]; + u32 clock_rate[CCI_RES_MAX]; +}; + +struct hw_params { + u16 thigh; + u16 tlow; + u16 tsu_sto; + u16 tsu_sta; + u16 thd_dat; + u16 thd_sta; + u16 tbuf; + u8 scl_stretch_en; + u16 trdhld; + u16 tsp; +}; + +struct cci_clock { + struct clk *clk; + const char *name; + u32 freq; +}; + +struct cci; + +struct cci_master { + struct i2c_adapter adap; + u16 master; + int status; + bool complete_pending; + struct completion irq_complete; + struct cci *cci; +}; + +struct cci { + struct device *dev; + void __iomem *base; + u32 irq; + struct clk_bulk_data *clock; + u32 *clock_freq; + int nclocks; + u16 queue_size[NUM_QUEUES]; + struct cci_master master[NUM_MASTERS]; +}; + +static const struct cci_res res_v1_0_8 = { + .clock = { + "camss_top_ahb", + "cci_ahb", + "camss_ahb", + "cci" + }, + .clock_rate = { + 0, + 80000000, + 0, + 19200000 + } +}; + +static const struct cci_res res_v1_4_0 = { + .clock = { "camss_top_ahb", + "cci_ahb", + "camss_ahb", + "cci" }, + .clock_rate = { 0, + 0, + 0, + 37500000 } +}; + +static const struct hw_params hw_params_v1_0_8[3] = { + { + /* I2C_MODE_STANDARD */ + .thigh = 78, + .tlow = 114, + .tsu_sto = 28, + .tsu_sta = 28, + .thd_dat = 10, + .thd_sta = 77, + .tbuf = 118, + .scl_stretch_en = 0, + .trdhld = 6, + .tsp = 1 + }, + { + /* I2C_MODE_FAST */ + .thigh = 20, + .tlow = 28, + .tsu_sto = 21, + .tsu_sta = 21, + .thd_dat = 13, + .thd_sta = 18, + .tbuf = 32, + .scl_stretch_en = 0, + .trdhld = 6, + .tsp = 3 + } +}; + +static const struct hw_params hw_params_v1_4_0[3] = { + { + /* I2C_MODE_STANDARD */ + .thigh = 201, + .tlow = 174, + .tsu_sto = 204, + .tsu_sta = 231, + .thd_dat = 22, + .thd_sta = 162, + .tbuf = 227, + .scl_stretch_en = 0, + .trdhld = 6, + .tsp = 3 + }, + { + /* I2C_MODE_FAST */ + .thigh = 38, + .tlow = 56, + .tsu_sto = 40, + .tsu_sta = 40, + .thd_dat = 22, + .thd_sta = 35, + .tbuf = 62, + .scl_stretch_en = 0, + .trdhld = 6, + .tsp = 3 + }, + { + /* I2C_MODE_FAST_PLUS */ + .thigh = 16, + .tlow = 22, + .tsu_sto = 17, + .tsu_sta = 18, + .thd_dat = 16, + .thd_sta = 15, + .tbuf = 24, + .scl_stretch_en = 0, + .trdhld = 3, + .tsp = 3 + } +}; + +static const u16 queue_0_size_v1_0_8 = 64; +static const u16 queue_1_size_v1_0_8 = 16; + +static const u16 queue_0_size_v1_4_0 = 64; +static const u16 queue_1_size_v1_4_0 = 16; + +/** + * cci_clock_set_rate() - Set clock frequency rates + * @nclocks: Number of clocks + * @clock: Clock array + * @clock_freq: Clock frequency rate array + * @dev: Device + * + * Return 0 on success or a negative error code otherwise + */ +static int cci_clock_set_rate(int nclocks, struct clk_bulk_data *clock, + u32 *clock_freq, struct device *dev) +{ + int i, ret; + long rate; + + for (i = 0; i < nclocks; i++) + if (clock_freq[i]) { + rate = clk_round_rate(clock[i].clk, clock_freq[i]); + if (rate < 0) { + dev_err(dev, "clk round rate failed: %ld\n", + rate); + return rate; + } + + ret = clk_set_rate(clock[i].clk, clock_freq[i]); + if (ret < 0) { + dev_err(dev, "clk set rate failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +static irqreturn_t cci_isr(int irq, void *dev) +{ + struct cci *cci = dev; + u32 val, reset = 0; + + val = readl(cci->base + CCI_IRQ_STATUS_0); + writel(val, cci->base + CCI_IRQ_CLEAR_0); + writel(0x1, cci->base + CCI_IRQ_GLOBAL_CLEAR_CMD); + + if (val & CCI_IRQ_STATUS_0_RST_DONE_ACK) { + if (cci->master[0].complete_pending) { + cci->master[0].complete_pending = false; + complete(&cci->master[0].irq_complete); + } + + if (cci->master[1].complete_pending) { + cci->master[1].complete_pending = false; + complete(&cci->master[1].irq_complete); + } + } + + if (val & CCI_IRQ_STATUS_0_I2C_M0_RD_DONE || + val & CCI_IRQ_STATUS_0_I2C_M0_Q0_REPORT || + val & CCI_IRQ_STATUS_0_I2C_M0_Q1_REPORT) { + cci->master[0].status = 0; + complete(&cci->master[0].irq_complete); + } + + if (val & CCI_IRQ_STATUS_0_I2C_M1_RD_DONE || + val & CCI_IRQ_STATUS_0_I2C_M1_Q0_REPORT || + val & CCI_IRQ_STATUS_0_I2C_M1_Q1_REPORT) { + cci->master[1].status = 0; + complete(&cci->master[1].irq_complete); + } + + if (unlikely(val & CCI_IRQ_STATUS_0_I2C_M0_Q0Q1_HALT_ACK)) { + cci->master[0].complete_pending = true; + reset = CCI_RESET_CMD_M0_MASK; + } + + if (unlikely(val & CCI_IRQ_STATUS_0_I2C_M1_Q0Q1_HALT_ACK)) { + cci->master[1].complete_pending = true; + reset = CCI_RESET_CMD_M1_MASK; + } + + if (unlikely(reset)) + writel(reset, cci->base + CCI_RESET_CMD); + + if (unlikely(val & CCI_IRQ_STATUS_0_I2C_M0_ERROR)) { + dev_err_ratelimited(cci->dev, "Master 0 error 0x%08x\n", val); + cci->master[0].status = -EIO; + writel(CCI_HALT_REQ_I2C_M0_Q0Q1, cci->base + CCI_HALT_REQ); + } + + if (unlikely(val & CCI_IRQ_STATUS_0_I2C_M1_ERROR)) { + dev_err_ratelimited(cci->dev, "Master 1 error 0x%08x\n", val); + cci->master[1].status = -EIO; + writel(CCI_HALT_REQ_I2C_M1_Q0Q1, cci->base + CCI_HALT_REQ); + } + + return IRQ_HANDLED; +} + +static void cci_halt(struct cci *cci) +{ + unsigned long time; + u32 val = CCI_HALT_REQ_I2C_M0_Q0Q1 | CCI_HALT_REQ_I2C_M1_Q0Q1; + + cci->master[0].complete_pending = true; + writel(val, cci->base + CCI_HALT_REQ); + + time = wait_for_completion_timeout + (&cci->master[0].irq_complete, + msecs_to_jiffies(CCI_TIMEOUT_MS)); + if (!time) + dev_err(cci->dev, "CCI halt timeout\n"); +} + +static int cci_reset(struct cci *cci) +{ + unsigned long time; + + cci->master[0].complete_pending = true; + writel(CCI_RESET_CMD_MASK, cci->base + CCI_RESET_CMD); + + time = wait_for_completion_timeout + (&cci->master[0].irq_complete, + msecs_to_jiffies(CCI_TIMEOUT_MS)); + if (!time) { + dev_err(cci->dev, "CCI reset timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int cci_init(struct cci *cci, const struct hw_params *hw) +{ + u32 val = CCI_IRQ_MASK_0_I2C_M0_RD_DONE | + CCI_IRQ_MASK_0_I2C_M0_Q0_REPORT | + CCI_IRQ_MASK_0_I2C_M0_Q1_REPORT | + CCI_IRQ_MASK_0_I2C_M1_RD_DONE | + CCI_IRQ_MASK_0_I2C_M1_Q0_REPORT | + CCI_IRQ_MASK_0_I2C_M1_Q1_REPORT | + CCI_IRQ_MASK_0_RST_DONE_ACK | + CCI_IRQ_MASK_0_I2C_M0_Q0Q1_HALT_ACK | + CCI_IRQ_MASK_0_I2C_M1_Q0Q1_HALT_ACK | + CCI_IRQ_MASK_0_I2C_M0_ERROR | + CCI_IRQ_MASK_0_I2C_M1_ERROR; + int i; + + writel(val, cci->base + CCI_IRQ_MASK_0); + + for (i = 0; i < NUM_MASTERS; i++) { + val = hw->thigh << 16 | hw->tlow; + writel(val, cci->base + CCI_I2C_Mm_SCL_CTL(i)); + + val = hw->tsu_sto << 16 | hw->tsu_sta; + writel(val, cci->base + CCI_I2C_Mm_SDA_CTL_0(i)); + + val = hw->thd_dat << 16 | hw->thd_sta; + writel(val, cci->base + CCI_I2C_Mm_SDA_CTL_1(i)); + + val = hw->tbuf; + writel(val, cci->base + CCI_I2C_Mm_SDA_CTL_2(i)); + + val = hw->scl_stretch_en << 8 | hw->trdhld << 4 | hw->tsp; + writel(val, cci->base + CCI_I2C_Mm_MISC_CTL(i)); + } + + return 0; +} + +static int cci_run_queue(struct cci *cci, u8 master, u8 queue) +{ + unsigned long time; + u32 val; + int ret; + + val = readl(cci->base + CCI_I2C_Mm_Qn_CUR_WORD_CNT(master, queue)); + writel(val, cci->base + CCI_I2C_Mm_Qn_EXEC_WORD_CNT(master, queue)); + + val = BIT(master * 2 + queue); + writel(val, cci->base + CCI_QUEUE_START); + + time = wait_for_completion_timeout(&cci->master[master].irq_complete, + msecs_to_jiffies(CCI_TIMEOUT_MS)); + if (!time) { + dev_err(cci->dev, "master %d queue %d timeout\n", + master, queue); + + cci_halt(cci); + + return -ETIMEDOUT; + } + + ret = cci->master[master].status; + if (ret < 0) + dev_err(cci->dev, "master %d queue %d error %d\n", + master, queue, ret); + + return ret; +} + +static int cci_validate_queue(struct cci *cci, u8 master, u8 queue) +{ + int ret = 0; + u32 val; + + val = readl(cci->base + CCI_I2C_Mm_Qn_CUR_WORD_CNT(master, queue)); + + if (val == cci->queue_size[queue]) + return -EINVAL; + + if (val) { + val = CCI_I2C_REPORT | BIT(8); + writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); + + ret = cci_run_queue(cci, master, queue); + } + + return ret; +} + +static int cci_i2c_read(struct cci *cci, u16 master, + u16 addr, u8 *buf, u16 len) +{ + u32 val, words_read, words_exp; + u8 queue = QUEUE_1; + int i, index = 0, ret; + bool first = 0; + + /* + * Call validate queue to make sure queue is empty before starting. + * This is to avoid overflow / underflow of queue. + */ + ret = cci_validate_queue(cci, master, queue); + if (ret < 0) + return ret; + + val = CCI_I2C_SET_PARAM | (addr & 0x7f) << 4; + writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); + + val = CCI_I2C_READ | len << 4; + writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); + + ret = cci_run_queue(cci, master, queue); + if (ret < 0) + return ret; + + words_read = readl(cci->base + CCI_I2C_Mm_READ_BUF_LEVEL(master)); + words_exp = len / 4 + 1; + if (words_read != words_exp) { + dev_err(cci->dev, "words read = %d, words expected = %d\n", + words_read, words_exp); + return -EIO; + } + + do { + val = readl(cci->base + CCI_I2C_Mm_READ_DATA(master)); + + for (i = 0; i < 4 && index < len; i++) { + if (first) { + first = false; + continue; + } + buf[index++] = (val >> (i * 8)) & 0xff; + } + } while (--words_read); + + return 0; +} + +static int cci_i2c_write(struct cci *cci, u16 master, + u16 addr, u8 *buf, u16 len) +{ + u8 queue = QUEUE_0; + u8 load[12] = { 0 }; + int i = 0, j, ret; + u32 val; + + /* + * Call validate queue to make sure queue is empty before starting. + * This is to avoid overflow / underflow of queue. + */ + ret = cci_validate_queue(cci, master, queue); + if (ret < 0) + return ret; + + val = CCI_I2C_SET_PARAM | (addr & 0x7f) << 4; + writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); + + load[i++] = CCI_I2C_WRITE | len << 4; + + for (j = 0; j < len; j++) + load[i++] = buf[j]; + + for (j = 0; j < i; j += 4) { + val = load[j]; + val |= load[j + 1] << 8; + val |= load[j + 2] << 16; + val |= load[j + 3] << 24; + writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); + } + + val = CCI_I2C_REPORT | BIT(8); + writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); + + return cci_run_queue(cci, master, queue); +} + +static int cci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct cci_master *cci_master = i2c_get_adapdata(adap); + struct cci *cci = cci_master->cci; + int i, ret = 0; + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + ret = cci_i2c_read(cci, cci_master->master, + msgs[i].addr, msgs[i].buf, + msgs[i].len); + else + ret = cci_i2c_write(cci, msgs[i].addr, + cci_master->master, + msgs[i].buf, msgs[i].len); + + if (ret < 0) { + dev_err(cci->dev, "cci i2c xfer error %d", ret); + break; + } + } + + if (!ret) + ret = num; + + return ret; +} + +static u32 cci_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm cci_algo = { + .master_xfer = cci_xfer, + .functionality = cci_func, +}; + +static const struct i2c_adapter_quirks cci_quirks_v1_0_8 = { + .max_write_len = 10, + .max_read_len = 12, +}; + +static const struct i2c_adapter_quirks cci_quirks_v1_4_0 = { + .max_write_len = 11, + .max_read_len = 12, +}; + +static int cci_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct cci_res *res; + const struct hw_params *hw; + struct cci *cci; + struct resource *r; + int ret = 0, i, i2c_error; + u32 val; + u8 mode; + + cci = devm_kzalloc(dev, sizeof(*cci), GFP_KERNEL); + if (!cci) + return -ENOMEM; + + cci->dev = dev; + platform_set_drvdata(pdev, cci); + + if (of_device_is_compatible(dev->of_node, "qcom,cci-v1.0.8")) { + res = &res_v1_0_8; + hw = hw_params_v1_0_8; + cci->queue_size[0] = queue_0_size_v1_0_8; + cci->queue_size[1] = queue_1_size_v1_0_8; + + for (i = 0; i < NUM_MASTERS; i++) + cci->master[i].adap.quirks = &cci_quirks_v1_0_8; + + } else if (of_device_is_compatible(dev->of_node, "qcom,cci-v1.4.0")) { + res = &res_v1_4_0; + hw = hw_params_v1_4_0; + cci->queue_size[0] = queue_0_size_v1_4_0; + cci->queue_size[1] = queue_1_size_v1_4_0; + + for (i = 0; i < NUM_MASTERS; i++) + cci->master[i].adap.quirks = &cci_quirks_v1_4_0; + + } else { + return -EINVAL; + } + + for (i = 0; i < NUM_MASTERS; i++) { + cci->master[i].adap.algo = &cci_algo; + cci->master[i].adap.dev.parent = cci->dev; + cci->master[i].adap.dev.of_node = dev->of_node; + cci->master[i].master = i; + cci->master[i].cci = cci; + i2c_set_adapdata(&cci->master[i].adap, cci); + snprintf(cci->master[i].adap.name, + sizeof(cci->master[i].adap.name), + "Qualcomm Camera Control Interface: %d", i); + } + + mode = I2C_MODE_STANDARD; + ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &val); + if (!ret) { + if (val == 400000) + mode = I2C_MODE_FAST; + else if (val == 1000000) + mode = I2C_MODE_FAST_PLUS; + } + + /* Memory */ + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cci->base = devm_ioremap_resource(dev, r); + if (IS_ERR(cci->base)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(cci->base); + } + + /* Interrupt */ + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "missing IRQ: %d\n", ret); + return ret; + } + cci->irq = ret; + + ret = devm_request_irq(dev, cci->irq, cci_isr, + IRQF_TRIGGER_RISING, dev_name(dev), cci); + if (ret < 0) { + dev_err(dev, "request_irq failed, ret: %d\n", ret); + return ret; + } + + disable_irq(cci->irq); + + /* Clocks */ + + cci->nclocks = 0; + while (res->clock[cci->nclocks]) + cci->nclocks++; + + cci->clock = devm_kzalloc(dev, cci->nclocks * + sizeof(*cci->clock), GFP_KERNEL); + if (!cci->clock) + return -ENOMEM; + + cci->clock_freq = devm_kzalloc(dev, cci->nclocks * + sizeof(*cci->clock_freq), GFP_KERNEL); + if (!cci->clock_freq) + return -ENOMEM; + + for (i = 0; i < cci->nclocks; i++) { + struct clk_bulk_data *clock = &cci->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->id = res->clock[i]; + cci->clock_freq[i] = res->clock_rate[i]; + } + + ret = cci_clock_set_rate(cci->nclocks, cci->clock, + cci->clock_freq, dev); + if (ret < 0) + return ret; + + ret = clk_bulk_prepare_enable(cci->nclocks, cci->clock); + if (ret < 0) + return ret; + + val = readl_relaxed(cci->base + CCI_HW_VERSION); + dev_dbg(dev, "%s: CCI HW version = 0x%08x", __func__, val); + + init_completion(&cci->master[0].irq_complete); + init_completion(&cci->master[1].irq_complete); + + enable_irq(cci->irq); + + ret = cci_reset(cci); + if (ret < 0) + goto error; + + ret = cci_init(cci, &hw[mode]); + if (ret < 0) + goto error; + + for (i = 0; i < NUM_MASTERS; i++) { + ret = i2c_add_adapter(&cci->master[i].adap); + if (ret < 0) { + i2c_error = i; + goto error_i2c; + } + } + + return 0; + +error_i2c: + for (i = 0; i < i2c_error; i++) + i2c_del_adapter(&cci->master[i].adap); +error: + clk_bulk_disable_unprepare(cci->nclocks, cci->clock); + + return ret; +} + +static int cci_remove(struct platform_device *pdev) +{ + struct cci *cci = platform_get_drvdata(pdev); + int i; + + disable_irq(cci->irq); + clk_bulk_disable_unprepare(cci->nclocks, cci->clock); + + for (i = 0; i < NUM_MASTERS; i++) + i2c_del_adapter(&cci->master[i].adap); + + return 0; +} + +static const struct of_device_id cci_dt_match[] = { + { .compatible = "qcom,cci-v1.0.8" }, + { .compatible = "qcom,cci-v1.4.0" }, + {} +}; +MODULE_DEVICE_TABLE(of, cci_dt_match); + +static struct platform_driver qcom_cci_driver = { + .probe = cci_probe, + .remove = cci_remove, + .driver = { + .name = "i2c-qcom-cci", + .of_match_table = cci_dt_match, + }, +}; + +module_platform_driver(qcom_cci_driver); + +MODULE_DESCRIPTION("Qualcomm Camera Control Interface driver"); +MODULE_AUTHOR("Todor Tomov "); +MODULE_LICENSE("GPL v2");