From patchwork Tue Jul 10 12:44:42 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vinod Koul X-Patchwork-Id: 141507 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp3837534ljj; Tue, 10 Jul 2018 05:45:19 -0700 (PDT) X-Google-Smtp-Source: AAOMgpdrJIt7gK4oDiwkg/2YfL9IZuhvwe1gZluMqfg3AxH1HZNCxb5eVi089C3jY4BreqUNYpJ+ X-Received: by 2002:a65:4587:: with SMTP id o7-v6mr22843235pgq.317.1531226719126; Tue, 10 Jul 2018 05:45:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531226719; cv=none; d=google.com; s=arc-20160816; b=BfMJfKmbz7bQzQQjAiKUCImg57oXdUKJj61Jt1rWx/xqHjbLp0TTmWE9fm0DcXy/Lf DFhYsxT/qq4aXvRSBWgexFpVVCpmFAyVonc4W+mfgTMp8PJ/JdZyYZnBil/KZP0zWd3h 2U2/ONERYGOrjJV9P93qsmml4Gm0+SRMkzWnmSkvM209A5OzU+R+MhWGa+B4OVx0BUhR GDe7M5jGHwQ4B3A/GcX4fn80DTDFkHooOqBT0OA1oDUFnAfzBRh8FIpQqUxa3SITkQJU R6XJ5mGJ0LDpnqdsmBvmny+cXp7cjWePh9H0fsnmASova+Zk7OnT1jpBWSekEdXnnIWS A3fQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:message-id:date:subject:cc:to:from :dkim-signature:arc-authentication-results; bh=buRKeGUI4w/ZJ28FDiqcaEC5OWqc6EQdpsAjaUuvaQM=; b=kRSWtNT33oiQDfg3NGBNHm32b7Shs4fW6sQ9c6iYZ2QUHMW8YxOr/7+dojGf/lgDuT SlP4ZE7lLoEcR5vXF4oOJFtJ6FCKJIXOy21QuhZHq2qr3kAiRhcCWLGepLF96Ed9v38b AaAsZlE6rzVGRCXDrsB155BOZO+0eCOr/NFs87AcEl6wghWBcWmK78Z6bprx1boMrL2X J55ZT94NQszwIyIPr9DBDeRnA2gDruOsubMr6x88BK2vlJEWCorX4t39vzT/HtilDOc1 0TlzaYaazmhMN2tFyxVqJ5AY1ynhHafaINgm3KrO7jPZ09NUrrD4mOhpk2loBNvGMHh1 ITJw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@kernel.org header.s=default header.b=VhxEJUDv; 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 m12-v6si7961062pgd.334.2018.07.10.05.45.18; Tue, 10 Jul 2018 05:45:19 -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=VhxEJUDv; 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 S933256AbeGJMpS (ORCPT + 5 others); Tue, 10 Jul 2018 08:45:18 -0400 Received: from mail.kernel.org ([198.145.29.99]:48856 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753072AbeGJMpR (ORCPT ); Tue, 10 Jul 2018 08:45:17 -0400 Received: from localhost.localdomain (unknown [106.200.232.130]) (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 239E8208E3; Tue, 10 Jul 2018 12:45:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1531226716; bh=4hmsnQrjT93Ja6/pq888701+ccOTu93J6FMUd2KRevQ=; h=From:To:Cc:Subject:Date:From; b=VhxEJUDv5oY1kcPMY/ZyAOMFJ6ql38pYnOulYpFKQ0dhJJFTbFgC1dr7GvovHJuKM 8fRBkwBqAEs8YmYc3bCagAn+9uU56xGbMZBTxqtXmrY/bF/Gu0OYg9sBDLmWYmw36h b6COHISlt9PPX7s3WJ3kkh/cKEYa/MSSwwykMuvs= From: Vinod Koul To: linux-i2c@vger.kernel.org Cc: Rob Herring , Wolfram Sang , devicetree@vger.kernel.org, Todor Tomov , Vinod Koul Subject: [PATCH 1/2] dt-bindings: i2c: Add binding document for Qualcomm CCI Date: Tue, 10 Jul 2018 18:14:42 +0530 Message-Id: <20180710124443.24932-1-vkoul@kernel.org> X-Mailer: git-send-email 2.14.4 Sender: devicetree-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org From: Todor Tomov Add DT binding document for Qualcomm Camera Control Interface device Signed-off-by: Todor Tomov Signed-off-by: Vinod Koul --- .../devicetree/bindings/i2c/i2c-qcom-cci.txt | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/i2c-qcom-cci.txt -- 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/Documentation/devicetree/bindings/i2c/i2c-qcom-cci.txt b/Documentation/devicetree/bindings/i2c/i2c-qcom-cci.txt new file mode 100644 index 000000000000..cb1fe158fba3 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-qcom-cci.txt @@ -0,0 +1,46 @@ +Qualcomm Camera Control Interface controller + +Required properties: + - compatible: Should be one of: + - "qcom,cci-v1.0.8" for 8916; + - "qcom,cci-v1.4.0" for 8996. + - #address-cells: Should be <1>. + - #size-cells: Should be <0>. + - reg: Base address of the controller and length of memory mapped region. + - interrupts: Specifier for CCI interrupt. + - clocks: List of clock specifiers, one for each entry in clock-names. + - clock-names: Should contain: + - "mmss_mmagic_ahb" - on 8996 only; + - "camss_top_ahb"; + - "cci_ahb"; + - "cci"; + - "camss_ahb". + +Required properties on 8996: + - power-domains: Power domain specifier. + +Optional: + - clock-frequency: Desired I2C bus clock frequency in Hz, defaults to 100 kHz + if omitted. + +Example: + + cci@a0c000 { + compatible = "qcom,cci-v1.4.0"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xa0c000 0x1000>; + interrupts = ; + power-domains = <&mmcc CAMSS_GDSC>; + clocks = <&mmcc MMSS_MMAGIC_AHB_CLK>, + <&mmcc CAMSS_TOP_AHB_CLK>, + <&mmcc CAMSS_CCI_AHB_CLK>, + <&mmcc CAMSS_CCI_CLK>, + <&mmcc CAMSS_AHB_CLK>; + clock-names = "mmss_mmagic_ahb", + "camss_top_ahb", + "cci_ahb", + "cci", + "camss_ahb"; + clock-frequency = <400000>; + }; From patchwork Tue Jul 10 12:44:43 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vinod Koul X-Patchwork-Id: 141508 Delivered-To: patch@linaro.org Received: by 2002:a2e:9754:0:0:0:0:0 with SMTP id f20-v6csp3837608ljj; Tue, 10 Jul 2018 05:45:22 -0700 (PDT) X-Google-Smtp-Source: AAOMgpeehysOtdwRPXfXJwtmuaxIzvzNo7I3BXMak4ccy8WXZsPq3jKCG0xAPXHbPLAXpAvIR/fs X-Received: by 2002:a17:902:7446:: with SMTP id e6-v6mr24962731plt.161.1531226722690; Tue, 10 Jul 2018 05:45:22 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531226722; cv=none; d=google.com; s=arc-20160816; b=d3MfNjoEjtEcq67xaCFgdweSI2Z1FA8vKubl4R/VjKx/OV1BnIbqIxEJmpd9by8hAC Ma7Z/CRMmZDsPXOz5MOJyMKaAlAt555rYC3fXiKaLyhH/lfpjDInZPl9NJIHVv38pl+8 PZnzTHxyrJ0g3sZ8N3zk7xYNiNwNeZONIYUFuPTJJnlqGABC18jWrGf2A/UPOoID1ar1 Xc4PDDL4rGc4rqtC9CZQ1kyYHfxZe+Bm35mJahILQg1uXsQi+grLJipEvz4Lek0iCO46 0Sby7vUTwdi+yRuiM+4KcXMK3pQi9v0IYGmwvG06b6yw7Szk2Dtk3StJlKkz/yHSBP8u PJJQ== 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=E6Uy61c86HXHrOD5wObab/Bwq2E/Rg1bvAGqV3dar38=; b=0fa/ZnVPfqVlA9wzWcLhsTNd0hhXpCYsuWqulLckiapdj8eG22lfm4MRuDkXEMpgux VGjzcXWpleCuEcpzojtwJnFJp3Fzp485JcHGgYR+Fc4I4Xrp6tEXDedUyEriGtrab0Ga Qg9ZW2mD/xrMN4PahLMcmRhxoK47RYaSwLps8CRfgvzaKNgqSz4Yydkh4muWDydcgwWT sQ2Nsfb2VhbC29KGY/woWLbI1zSjVYNE11E4kCHE9GikqcRb4r3Ln2Rx4N2165/4NHrf Mes+2GBE/Q6aDLEZZ7cdhsj4bkRfBO2qhN2hTxpndSB1jjyKXluo3tcNKZGKMaAqh564 Accg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@kernel.org header.s=default header.b="O13F/nKC"; 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 m12-v6si7961062pgd.334.2018.07.10.05.45.22; Tue, 10 Jul 2018 05:45:22 -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="O13F/nKC"; 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 S933279AbeGJMpV (ORCPT + 5 others); Tue, 10 Jul 2018 08:45:21 -0400 Received: from mail.kernel.org ([198.145.29.99]:48898 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753072AbeGJMpT (ORCPT ); Tue, 10 Jul 2018 08:45:19 -0400 Received: from localhost.localdomain (unknown [106.200.232.130]) (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 565F3208E5; Tue, 10 Jul 2018 12:45:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1531226719; bh=suUz4WyupDfMDqLRe8p+dQx53r8+ypBr+A9c+X0dEpk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=O13F/nKChS58K/uPyQTvtbLY20NsP+qTOn5yPAePzA/zLeoQe97MMzw6sUUMU0phn JMawm89AvgFQQKoZyA1gBOtjV53Iy6jE72yiFG7OfGQBSN19gMyuuJYS9oZpzys4qQ hgkaBLY/UN0hNHaEKnAsYNzfMfdICiqZwWFE6J38= From: Vinod Koul To: linux-i2c@vger.kernel.org Cc: Rob Herring , Wolfram Sang , devicetree@vger.kernel.org, Todor Tomov , Vinod Koul Subject: [PATCH 2/2] i2c: Add Qualcomm Camera Control Interface driver Date: Tue, 10 Jul 2018 18:14:43 +0530 Message-Id: <20180710124443.24932-2-vkoul@kernel.org> X-Mailer: git-send-email 2.14.4 In-Reply-To: <20180710124443.24932-1-vkoul@kernel.org> References: <20180710124443.24932-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 - support for the only CCI I2C bus; - 1.4.0 - found on MSM8996/APQ8096 - support for first (out of two) 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 | 767 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 778 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..6c6f3aec40b1 --- /dev/null +++ b/drivers/i2c/busses/i2c-qcom-cci.c @@ -0,0 +1,767 @@ +// 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 1 +#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_master { + int status; + bool complete_pending; + struct completion irq_complete; +}; + +struct cci { + struct device *dev; + struct i2c_adapter adap; + 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; + + for (i = 0; i < nclocks; i++) + if (clock_freq[i]) { + long rate; + int ret; + + 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 reset = 0; + u32 val; + + 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 addr, u8 *buf, u16 len) +{ + u8 master = 0; + u8 queue = QUEUE_1; + u32 val; + u32 words_read, words_exp; + int i, index; + bool first; + int ret; + + /* + * 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; + } + + index = 0; + first = true; + 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 addr, u8 *buf, u16 len) +{ + u8 master = 0; + u8 queue = QUEUE_0; + u8 load[12] = { 0 }; + int i, j; + u32 val; + int ret; + + /* + * 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)); + + i = 0; + 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 *cci = i2c_get_adapdata(adap); + int i; + int ret = 0; + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + ret = cci_i2c_read(cci, msgs[i].addr, msgs[i].buf, + msgs[i].len); + else + ret = cci_i2c_write(cci, msgs[i].addr, 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; + u8 mode; + u32 val; + int i; + + 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; + cci->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; + cci->adap.quirks = &cci_quirks_v1_4_0; + } else { + return -EINVAL; + } + + cci->adap.algo = &cci_algo; + cci->adap.dev.parent = cci->dev; + cci->adap.dev.of_node = dev->of_node; + i2c_set_adapdata(&cci->adap, cci); + + strlcpy(cci->adap.name, "Qualcomm Camera Control Interface", + sizeof(cci->adap.name)); + + 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; + + ret = i2c_add_adapter(&cci->adap); + if (ret < 0) + goto error; + + return 0; + +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); + + disable_irq(cci->irq); + clk_bulk_disable_unprepare(cci->nclocks, cci->clock); + + i2c_del_adapter(&cci->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");