From nobody Thu Apr 2 12:13:09 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 51A533B636F; Mon, 30 Mar 2026 08:21:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858921; cv=none; b=qUu2wtLRGudYWsM+NsX/glkz0VAs1IzuN5h37ynhg/ubAuDd3VMSbgoj0P1oPVaxjOlgsHwNsNBt7gxvYJghcmLgHLK7cH08/+Xa5CnKbB2B75kNHhK+KrXfhGGdGqM8wYnnQch8yrEDFEIVjLs8LTvaF8m6myv4jHfu6BqW/Zc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858921; c=relaxed/simple; bh=cVF//4Im50k5fb4Tq1B2xo1LcWJgiSFw1Gq1Rm0z8vk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=NHcAwpAr8kGoAChCNG9VXKck55PbR9HHzE7YwR5PXNQm8yfdLFj6w5MJBWWOtD8gny/4z6+RFTOKLa2/8KTtCRPfKXW4FpWC7gZw4kwfyZQoAU7N3GcML1UQRUICAEbOJuoY+A8Pf3xTXQQQAW0w+Sas4xrDGGHB/cBpnRrswMA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Mon, 30 Mar 2026 16:21:47 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Mon, 30 Mar 2026 16:21:47 +0800 From: Ryan Chen Date: Mon, 30 Mar 2026 16:21:48 +0800 Subject: [PATCH v28 3/4] i2c: ast2600: Add controller driver for AST2600 new register set Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260330-upstream_i2c-v28-3-17bdae39c5cb@aspeedtech.com> References: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> In-Reply-To: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> To: , , Andi Shyti , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , "Benjamin Herrenschmidt" , Rayn Chen , Philipp Zabel CC: , , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774858906; l=40481; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=cVF//4Im50k5fb4Tq1B2xo1LcWJgiSFw1Gq1Rm0z8vk=; b=XJSi2hVibTN04pO/KQePZZcpA9gz70IF+pSkDFxz21x6/jPmRtBhHm4QxdJoyOMlRA0j/SxIr Rs1krY0/9YdB6p2XQTJ0jmxO1k7aAPJn/YgKdO96eIijBqMKlx5A/x+ X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= The AST2600 introduces a new I2C controller register layout, selectable at runtime via global control registers. Compared to the legacy layout used on AST2400/AST2500, the new layout separates controller (master) and target (slave) registers and adds support for packet-based transfers The new register set extends the hardware capabilities with: - Enhanced clock divider configuration for improved timing precision - tCKHighMin timing control for SCL high pulse width - Dual pool buffer mode (separate Tx/Rx buffers) - Extended DMA support with larger buffer size and alignment handling - Dedicated DMA buffers for controller and target directions - Hardware-assisted bus recovery and timeout mechanisms This patch adds an AST2600-specific I2C controller driver implementing the new register layout, including support for packet-based transfers and byte, buffer and DMA transfer modes. The legacy and new register layouts represent the same AST2600 I2C controller IP and therefore share the existing compatible string: "aspeed,ast2600-i2c-bus" To preserve DT ABI compatibility, driver selection is performed at probe time based on DT contents. In particular, the new binding requires the `aspeed,global-regs` phandle, which is absent from legacy DTBs: - The new driver only probes successfully when `aspeed,global-regs` is present. - The existing i2c-aspeed driver returns -ENODEV for AST2600 nodes that provide `aspeed,global-regs`, allowing the new driver to bind. Signed-off-by: Ryan Chen --- Changes in v28: - Separate xfer_mode_store into distinct parse and availability-check steps by introducing ast2600_i2c_xfer_mode_check() - fix tx dma memcpy source point address. - Use a temporary variable for devm_platform_get_and_ioremap_resource() to avoid storing an ERR_PTR in i2c_bus->buf_base; drop the redundant NULL assignment in the error path since i2c_bus is kzalloc()ed - Add ABI documentation file Documentation/ABI/testing/sysfs-driver-ast2600-i2c Changes in v27: - remove aspeed,transfer-mode selection instead aspeed,dma-mode. - add sysfs for xfer mode. Changes in v25: - Rename AST2600_I2CM_SMBUS_ALT to AST2600_I2CM_SMBUS_ALERT. - Refactor transfer mode handling using setup_tx/setup_rx helpers. - Rework DMA handling to use pre-allocated buffers and reduce mapping overhead in interrupt context. - Fix IRQ status checks to use consistent (sts & value) style. - Move device_property_read_bool() to probe(). - Improve probe error handling. - Handle timeout condition in target_byte_irq(). - Rename "package" to "packet". - Remove target reset when master wait_for_completion_timeout(). --- Documentation/ABI/testing/sysfs-driver-ast2600-i2c | 19 + drivers/i2c/busses/Makefile | 2 +- drivers/i2c/busses/i2c-aspeed.c | 5 + drivers/i2c/busses/i2c-ast2600.c | 1080 ++++++++++++++++= ++++ 4 files changed, 1105 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-driver-ast2600-i2c b/Documenta= tion/ABI/testing/sysfs-driver-ast2600-i2c new file mode 100644 index 000000000000..7d2a69a7281a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-ast2600-i2c @@ -0,0 +1,19 @@ +What: /sys/bus/platform/drivers/i2c-ast2600/.../xfer_mode +Date: March 2026 +KernelVersion: 7.x +Contact: Ryan Chen +Description: Shows or sets the active transfer mode for an ASPEED AST2600 + I2C controller instance. + + Possible values: + + =3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + byte Programmed I/O, one byte at a time. + buffer Programmed I/O using the hardware FIFO buffer. + Only available if the controller has a buffer + resource defined in the device tree. + dma DMA transfer (if DMA is available for this + controller). + =3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + Writing an unsupported or unavailable mode returns -EINVAL. diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 547123ab351f..ece201a67d41 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -37,7 +37,7 @@ obj-$(CONFIG_I2C_POWERMAC) +=3D i2c-powermac.o obj-$(CONFIG_I2C_ALTERA) +=3D i2c-altera.o obj-$(CONFIG_I2C_AMD_MP2) +=3D i2c-amd-mp2-pci.o i2c-amd-mp2-plat.o obj-$(CONFIG_I2C_AMD_ASF) +=3D i2c-amd-asf-plat.o -obj-$(CONFIG_I2C_ASPEED) +=3D i2c-aspeed.o +obj-$(CONFIG_I2C_ASPEED) +=3D i2c-aspeed.o i2c-ast2600.o obj-$(CONFIG_I2C_AT91) +=3D i2c-at91.o i2c-at91-y :=3D i2c-at91-core.o i2c-at91-master.o i2c-at91-$(CONFIG_I2C_AT91_SLAVE_EXPERIMENTAL) +=3D i2c-at91-slave.o diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspee= d.c index a26b74c71206..8286fd2cd130 100644 --- a/drivers/i2c/busses/i2c-aspeed.c +++ b/drivers/i2c/busses/i2c-aspeed.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include =20 @@ -1002,6 +1003,10 @@ static int aspeed_i2c_probe_bus(struct platform_devi= ce *pdev) struct clk *parent_clk; int irq, ret; =20 + if (device_is_compatible(&pdev->dev, "aspeed,ast2600-i2c-bus") && + device_property_present(&pdev->dev, "aspeed,global-regs")) + return -ENODEV; + bus =3D devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); if (!bus) return -ENOMEM; diff --git a/drivers/i2c/busses/i2c-ast2600.c b/drivers/i2c/busses/i2c-ast2= 600.c new file mode 100644 index 000000000000..c2368ba309a7 --- /dev/null +++ b/drivers/i2c/busses/i2c-ast2600.c @@ -0,0 +1,1080 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ASPEED AST2600 new register set I2C controller driver + * + * Copyright (C) 2026 ASPEED Technology Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AST2600_I2CG_ISR 0x00 +#define AST2600_I2CG_SLAVE_ISR 0x04 +#define AST2600_I2CG_OWNER 0x08 +#define AST2600_I2CG_CTRL 0x0C +#define AST2600_I2CG_CLK_DIV_CTRL 0x10 + +#define AST2600_I2CG_SLAVE_PKT_NAK BIT(4) +#define AST2600_I2CG_M_S_SEPARATE_INTR BIT(3) +#define AST2600_I2CG_CTRL_NEW_REG BIT(2) +#define AST2600_I2CG_CTRL_NEW_CLK_DIV BIT(1) +#define AST2600_GLOBAL_INIT \ + (AST2600_I2CG_CTRL_NEW_REG | AST2600_I2CG_CTRL_NEW_CLK_DIV) +/* + * APB clk : 100Mhz + * div : scl : baseclk [APB/((div/2) + 1)] : tBuf [1/bclk * 16] + * I2CG10[31:24] base clk4 for i2c auto recovery timeout counter (0xC6) + * I2CG10[23:16] base clk3 for Standard-mode (100Khz) min tBuf 4.7us + * 0x3c : 100.8Khz : 3.225Mhz : 4.96us + * 0x3d : 99.2Khz : 3.174Mhz : 5.04us + * 0x3e : 97.65Khz : 3.125Mhz : 5.12us + * 0x40 : 97.75Khz : 3.03Mhz : 5.28us + * 0x41 : 99.5Khz : 2.98Mhz : 5.36us (default) + * I2CG10[15:8] base clk2 for Fast-mode (400Khz) min tBuf 1.3us + * 0x12 : 400Khz : 10Mhz : 1.6us + * I2CG10[7:0] base clk1 for Fast-mode Plus (1Mhz) min tBuf 0.5us + * 0x08 : 1Mhz : 20Mhz : 0.8us + */ +#define I2CCG_DIV_CTRL 0xC6411208 + +/* 0x00 : I2CC Controller/Target Function Control Register */ +#define AST2600_I2CC_FUN_CTRL 0x00 +#define AST2600_I2CC_SLAVE_ADDR_RX_EN BIT(20) +#define AST2600_I2CC_MASTER_RETRY_MASK GENMASK(19, 18) +#define AST2600_I2CC_MASTER_RETRY(x) (((x) & GENMASK(1, 0)) << 18) +#define AST2600_I2CC_BUS_AUTO_RELEASE BIT(17) +#define AST2600_I2CC_M_SDA_LOCK_EN BIT(16) +#define AST2600_I2CC_MULTI_MASTER_DIS BIT(15) +#define AST2600_I2CC_M_SCL_DRIVE_EN BIT(14) +#define AST2600_I2CC_MSB_STS BIT(9) +#define AST2600_I2CC_SDA_DRIVE_1T_EN BIT(8) +#define AST2600_I2CC_M_SDA_DRIVE_1T_EN BIT(7) +#define AST2600_I2CC_M_HIGH_SPEED_EN BIT(6) +/* reserver 5 : 2 */ +#define AST2600_I2CC_SLAVE_EN BIT(1) +#define AST2600_I2CC_MASTER_EN BIT(0) + +/* 0x04 : I2CC Controller/Target Clock and AC Timing Control Register #1 */ +#define AST2600_I2CC_AC_TIMING 0x04 +#define AST2600_I2CC_TTIMEOUT(x) (((x) & GENMASK(4, 0)) << 24) +#define AST2600_I2CC_TCKHIGHMIN(x) (((x) & GENMASK(3, 0)) << 20) +#define AST2600_I2CC_TCKHIGH(x) (((x) & GENMASK(3, 0)) << 16) +#define AST2600_I2CC_TCKLOW(x) (((x) & GENMASK(3, 0)) << 12) +#define AST2600_I2CC_THDDAT(x) (((x) & GENMASK(1, 0)) << 10) +#define AST2600_I2CC_TOUTBASECLK(x) (((x) & GENMASK(1, 0)) << 8) +#define AST2600_I2CC_TBASECLK(x) ((x) & GENMASK(3, 0)) +#define AST2600_I2CC_AC_TIMING_MASK GENMASK(23, 0) + +/* 0x08 : I2CC Controller/Target Transmit/Receive Byte Buffer Register */ +#define AST2600_I2CC_STS_AND_BUFF 0x08 +#define AST2600_I2CC_TX_DIR_MASK GENMASK(31, 29) +#define AST2600_I2CC_SDA_OE BIT(28) +#define AST2600_I2CC_SDA_O BIT(27) +#define AST2600_I2CC_SCL_OE BIT(26) +#define AST2600_I2CC_SCL_O BIT(25) + +#define AST2600_I2CC_SCL_LINE_STS BIT(18) +#define AST2600_I2CC_SDA_LINE_STS BIT(17) +#define AST2600_I2CC_BUS_BUSY_STS BIT(16) + +#define AST2600_I2CC_GET_RX_BUFF(x) (((x) >> 8) & GENMASK(7, 0)) + +/* 0x0C : I2CC Controller/Target Pool Buffer Control Register */ +#define AST2600_I2CC_BUFF_CTRL 0x0C +#define AST2600_I2CC_GET_RX_BUF_LEN(x) (((x) & GENMASK(29, 24)) >> 24) +#define AST2600_I2CC_SET_RX_BUF_LEN(x) (((((x) - 1) & GENMASK(4, 0)) << 1= 6) | BIT(0)) +#define AST2600_I2CC_SET_TX_BUF_LEN(x) (((((x) - 1) & GENMASK(4, 0)) << 8= ) | BIT(0)) +#define AST2600_I2CC_GET_TX_BUF_LEN(x) ((((x) & GENMASK(12, 8)) >> 8)= + 1) + +/* 0x10 : I2CM Controller Interrupt Control Register */ +#define AST2600_I2CM_IER 0x10 +/* 0x14 : I2CM Controller Interrupt Status Register : WC */ +#define AST2600_I2CM_ISR 0x14 + +#define AST2600_I2CM_PKT_TIMEOUT BIT(18) +#define AST2600_I2CM_PKT_ERROR BIT(17) +#define AST2600_I2CM_PKT_DONE BIT(16) + +#define AST2600_I2CM_BUS_RECOVER_FAIL BIT(15) +#define AST2600_I2CM_SDA_DL_TO BIT(14) +#define AST2600_I2CM_BUS_RECOVER BIT(13) +#define AST2600_I2CM_SMBUS_ALERT BIT(12) + +#define AST2600_I2CM_SCL_LOW_TO BIT(6) +#define AST2600_I2CM_ABNORMAL BIT(5) +#define AST2600_I2CM_NORMAL_STOP BIT(4) +#define AST2600_I2CM_ARBIT_LOSS BIT(3) +#define AST2600_I2CM_RX_DONE BIT(2) +#define AST2600_I2CM_TX_NAK BIT(1) +#define AST2600_I2CM_TX_ACK BIT(0) + +/* 0x18 : I2CM Controller Command/Status Register */ +#define AST2600_I2CM_CMD_STS 0x18 +#define AST2600_I2CM_PKT_ADDR(x) (((x) & GENMASK(6, 0)) << 24) +#define AST2600_I2CM_PKT_EN BIT(16) +#define AST2600_I2CM_SDA_OE_OUT_DIR BIT(15) +#define AST2600_I2CM_SDA_O_OUT_DIR BIT(14) +#define AST2600_I2CM_SCL_OE_OUT_DIR BIT(13) +#define AST2600_I2CM_SCL_O_OUT_DIR BIT(12) +#define AST2600_I2CM_RECOVER_CMD_EN BIT(11) + +#define AST2600_I2CM_RX_DMA_EN BIT(9) +#define AST2600_I2CM_TX_DMA_EN BIT(8) +/* Command Bit */ +#define AST2600_I2CM_RX_BUFF_EN BIT(7) +#define AST2600_I2CM_TX_BUFF_EN BIT(6) +#define AST2600_I2CM_STOP_CMD BIT(5) +#define AST2600_I2CM_RX_CMD_LAST BIT(4) +#define AST2600_I2CM_RX_CMD BIT(3) + +#define AST2600_I2CM_TX_CMD BIT(1) +#define AST2600_I2CM_START_CMD BIT(0) + +/* 0x1C : I2CM Controller DMA Transfer Length Register */ +#define AST2600_I2CM_DMA_LEN 0x1C +/* Tx Rx support length 1 ~ 4096 */ +#define AST2600_I2CM_SET_RX_DMA_LEN(x) ((((x) & GENMASK(11, 0)) << 16) | B= IT(31)) +#define AST2600_I2CM_SET_TX_DMA_LEN(x) (((x) & GENMASK(11, 0)) | BIT(15)) + +/* 0x20 : I2CS Target Interrupt Control Register */ +#define AST2600_I2CS_IER 0x20 +/* 0x24 : I2CS Target Interrupt Status Register */ +#define AST2600_I2CS_ISR 0x24 + +#define AST2600_I2CS_ADDR_INDICATE_MASK GENMASK(31, 30) +#define AST2600_I2CS_SLAVE_PENDING BIT(29) + +#define AST2600_I2CS_WAIT_TX_DMA BIT(25) +#define AST2600_I2CS_WAIT_RX_DMA BIT(24) + +#define AST2600_I2CS_ADDR3_NAK BIT(22) +#define AST2600_I2CS_ADDR2_NAK BIT(21) +#define AST2600_I2CS_ADDR1_NAK BIT(20) + +#define AST2600_I2CS_ADDR_MASK GENMASK(19, 18) +#define AST2600_I2CS_PKT_ERROR BIT(17) +#define AST2600_I2CS_PKT_DONE BIT(16) +#define AST2600_I2CS_INACTIVE_TO BIT(15) + +#define AST2600_I2CS_SLAVE_MATCH BIT(7) +#define AST2600_I2CS_ABNOR_STOP BIT(5) +#define AST2600_I2CS_STOP BIT(4) +#define AST2600_I2CS_RX_DONE_NAK BIT(3) +#define AST2600_I2CS_RX_DONE BIT(2) +#define AST2600_I2CS_TX_NAK BIT(1) +#define AST2600_I2CS_TX_ACK BIT(0) + +/* 0x28 : I2CS Target CMD/Status Register */ +#define AST2600_I2CS_CMD_STS 0x28 +#define AST2600_I2CS_ACTIVE_ALL GENMASK(18, 17) +#define AST2600_I2CS_PKT_MODE_EN BIT(16) +#define AST2600_I2CS_AUTO_NAK_NOADDR BIT(15) +#define AST2600_I2CS_AUTO_NAK_EN BIT(14) + +#define AST2600_I2CS_ALT_EN BIT(10) +#define AST2600_I2CS_RX_DMA_EN BIT(9) +#define AST2600_I2CS_TX_DMA_EN BIT(8) +#define AST2600_I2CS_RX_BUFF_EN BIT(7) +#define AST2600_I2CS_TX_BUFF_EN BIT(6) +#define AST2600_I2CS_RX_CMD_LAST BIT(4) + +#define AST2600_I2CS_TX_CMD BIT(2) + +#define AST2600_I2CS_DMA_LEN 0x2C +#define AST2600_I2CS_SET_RX_DMA_LEN(x) (((((x) - 1) & GENMASK(11, 0)) << 1= 6) | BIT(31)) +#define AST2600_I2CS_SET_TX_DMA_LEN(x) ((((x) - 1) & GENMASK(11, 0)) | BIT= (15)) + +/* I2CM Controller DMA Tx Buffer Register */ +#define AST2600_I2CM_TX_DMA 0x30 +/* I2CM Controller DMA Rx Buffer Register */ +#define AST2600_I2CM_RX_DMA 0x34 +/* I2CS Target DMA Tx Buffer Register */ +#define AST2600_I2CS_TX_DMA 0x38 +/* I2CS Target DMA Rx Buffer Register */ +#define AST2600_I2CS_RX_DMA 0x3C + +#define AST2600_I2CS_ADDR_CTRL 0x40 + +#define AST2600_I2CS_ADDR3_MASK GENMASK(22, 16) +#define AST2600_I2CS_ADDR2_MASK GENMASK(14, 8) +#define AST2600_I2CS_ADDR1_MASK GENMASK(6, 0) + +#define AST2600_I2CM_DMA_LEN_STS 0x48 +#define AST2600_I2CS_DMA_LEN_STS 0x4C + +#define AST2600_I2C_GET_TX_DMA_LEN(x) ((x) & GENMASK(12, 0)) +#define AST2600_I2C_GET_RX_DMA_LEN(x) (((x) & GENMASK(28, 16)) >> 1= 6) + +/* 0x40 : Target Device Address Register */ +#define AST2600_I2CS_ADDR3_ENABLE BIT(23) +#define AST2600_I2CS_ADDR3(x) ((x) << 16) +#define AST2600_I2CS_ADDR2_ENABLE BIT(15) +#define AST2600_I2CS_ADDR2(x) ((x) << 8) +#define AST2600_I2CS_ADDR1_ENABLE BIT(7) +#define AST2600_I2CS_ADDR1(x) (x) + +#define I2C_TARGET_MSG_BUF_SIZE 4096 + +#define AST2600_I2C_DMA_SIZE 4096 + +#define CONTROLLER_TRIGGER_LAST_STOP (AST2600_I2CM_RX_CMD_LAST | AST2600_I= 2CM_STOP_CMD) +#define TARGET_TRIGGER_CMD (AST2600_I2CS_ACTIVE_ALL | AST2600_I2CS_PKT_MOD= E_EN) + +#define AST_I2C_TIMEOUT_CLK 0x1 + +enum xfer_mode { + BYTE_MODE, + BUFF_MODE, + DMA_MODE, +}; + +struct ast2600_i2c_bus { + struct i2c_adapter adap; + struct device *dev; + void __iomem *reg_base; + struct regmap *global_regs; + struct clk *clk; + struct i2c_timings timing_info; + struct completion cmd_complete; + struct i2c_msg *msgs; + u8 *controller_dma_buf; + dma_addr_t controller_dma_addr; + u32 apb_clk; + u32 timeout; + int irq; + int cmd_err; + int msgs_index; + int msgs_count; + int controller_xfer_cnt; + size_t buf_index; + size_t buf_size; + enum xfer_mode mode; + bool dma_available; + bool multi_master; + /* Buffer mode */ + void __iomem *buf_base; + int (*setup_tx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); + int (*setup_rx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); +}; + +static void ast2600_i2c_ac_timing_config(struct ast2600_i2c_bus *i2c_bus) +{ + unsigned long base_clk[16]; + int baseclk_idx =3D 0; + int divisor =3D 0; + u32 clk_div_reg; + u32 scl_low; + u32 scl_high; + u32 data; + + regmap_read(i2c_bus->global_regs, AST2600_I2CG_CLK_DIV_CTRL, &clk_div_reg= ); + + for (int i =3D 0; i < ARRAY_SIZE(base_clk); i++) { + if (i =3D=3D 0) + base_clk[i] =3D i2c_bus->apb_clk; + else if (i < 5) + base_clk[i] =3D (i2c_bus->apb_clk * 2) / + (((clk_div_reg >> ((i - 1) * 8)) & GENMASK(7, 0)) + 2); + else + base_clk[i] =3D base_clk[4] >> (i - 4); + + if ((base_clk[i] / i2c_bus->timing_info.bus_freq_hz) <=3D 32) { + baseclk_idx =3D i; + divisor =3D DIV_ROUND_UP(base_clk[i], i2c_bus->timing_info.bus_freq_hz); + break; + } + } + baseclk_idx =3D min(baseclk_idx, 15); + divisor =3D min(divisor, 32); + scl_low =3D min(divisor * 9 / 16 - 1, 15); + scl_high =3D (divisor - scl_low - 2) & GENMASK(3, 0); + data =3D (scl_high - 1) << 20 | scl_high << 16 | scl_low << 12 | baseclk_= idx; + if (i2c_bus->timeout) { + data |=3D AST2600_I2CC_TOUTBASECLK(AST_I2C_TIMEOUT_CLK); + data |=3D AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); + } + + writel(data, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); +} + +static int ast2600_i2c_recover_bus(struct ast2600_i2c_bus *i2c_bus) +{ + u32 state =3D readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + int ret =3D 0; + u32 ctrl; + int r; + + dev_dbg(i2c_bus->dev, "%d-bus recovery bus [%x]\n", i2c_bus->adap.nr, sta= te); + + /* reset controller */ + ctrl =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(ctrl & ~AST2600_I2CC_MASTER_EN, i2c_bus->reg_base + AST2600_I2CC_F= UN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + reinit_completion(&i2c_bus->cmd_complete); + i2c_bus->cmd_err =3D 0; + + /* Check SDA/SCL status in the status register. */ + state =3D readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + if (!(state & AST2600_I2CC_SDA_LINE_STS) && (state & AST2600_I2CC_SCL_LIN= E_STS)) { + writel(AST2600_I2CM_RECOVER_CMD_EN, i2c_bus->reg_base + AST2600_I2CM_CMD= _STS); + r =3D wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->adap.= timeout); + if (r =3D=3D 0) { + dev_dbg(i2c_bus->dev, "recovery timed out\n"); + return -ETIMEDOUT; + } else if (i2c_bus->cmd_err) { + dev_dbg(i2c_bus->dev, "recovery error\n"); + ret =3D -EPROTO; + } + } + + /* Recovery done */ + state =3D readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + if (state & AST2600_I2CC_BUS_BUSY_STS) { + dev_dbg(i2c_bus->dev, "Can't recover bus [%x]\n", state); + ret =3D -EPROTO; + } + + return ret; +} + +static int ast2600_i2c_setup_dma_tx(u32 cmd, struct ast2600_i2c_bus *i2c_b= us) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN; + + if (xfer_len > AST2600_I2C_DMA_SIZE) + xfer_len =3D AST2600_I2C_DMA_SIZE; + else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) + cmd |=3D AST2600_I2CM_STOP_CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (xfer_len) { + memcpy(i2c_bus->controller_dma_buf, + msg->buf + i2c_bus->controller_xfer_cnt, xfer_len); + cmd |=3D AST2600_I2CM_TX_DMA_EN | AST2600_I2CM_TX_CMD; + writel(AST2600_I2CM_SET_TX_DMA_LEN(xfer_len - 1), + i2c_bus->reg_base + AST2600_I2CM_DMA_LEN); + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_buff_tx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + u32 wbuf_dword; + int i; + + cmd |=3D AST2600_I2CM_PKT_EN; + + if (xfer_len > i2c_bus->buf_size) + xfer_len =3D i2c_bus->buf_size; + else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) + cmd |=3D AST2600_I2CM_STOP_CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (xfer_len) { + cmd |=3D AST2600_I2CM_TX_BUFF_EN | AST2600_I2CM_TX_CMD; + /* + * The controller's buffer register supports dword writes only. + * Therefore, write dwords to the buffer register in a 4-byte aligned, + * and write the remaining unaligned data at the end. + */ + for (i =3D 0; i < xfer_len; i +=3D 4) { + int xfer_cnt =3D i2c_bus->controller_xfer_cnt + i; + + switch (min(xfer_len - i, 4) % 4) { + case 1: + wbuf_dword =3D msg->buf[xfer_cnt]; + break; + case 2: + wbuf_dword =3D get_unaligned_le16(&msg->buf[xfer_cnt]); + break; + case 3: + wbuf_dword =3D get_unaligned_le24(&msg->buf[xfer_cnt]); + break; + default: + wbuf_dword =3D get_unaligned_le32(&msg->buf[xfer_cnt]); + break; + } + writel(wbuf_dword, i2c_bus->buf_base + i); + } + writel(AST2600_I2CC_SET_TX_BUF_LEN(xfer_len), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_byte_tx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len; + + xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if ((i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) && + ((i2c_bus->controller_xfer_cnt + 1) =3D=3D msg->len)) + cmd |=3D AST2600_I2CM_STOP_CMD; + + if (xfer_len) { + cmd |=3D AST2600_I2CM_TX_CMD; + writel(msg->buf[i2c_bus->controller_xfer_cnt], + i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_dma_rx(u32 cmd, struct ast2600_i2c_bus *i2c_b= us) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_DMA_EN | AST2600_I2CM_RX_C= MD; + + if (msg->flags & I2C_M_RECV_LEN) + xfer_len =3D 1; + else if (xfer_len > AST2600_I2C_DMA_SIZE) + xfer_len =3D AST2600_I2C_DMA_SIZE; + else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) + cmd |=3D CONTROLLER_TRIGGER_LAST_STOP; + + writel(AST2600_I2CM_SET_RX_DMA_LEN(xfer_len - 1), i2c_bus->reg_base + AST= 2600_I2CM_DMA_LEN); + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_buff_rx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_BUFF_EN | AST2600_I2CM_RX_= CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (msg->flags & I2C_M_RECV_LEN) { + dev_dbg(i2c_bus->dev, "smbus read\n"); + xfer_len =3D 1; + } else if (xfer_len > i2c_bus->buf_size) { + xfer_len =3D i2c_bus->buf_size; + } else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) { + cmd |=3D CONTROLLER_TRIGGER_LAST_STOP; + } + writel(AST2600_I2CC_SET_RX_BUF_LEN(xfer_len), i2c_bus->reg_base + AST2600= _I2CC_BUFF_CTRL); + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_byte_rx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + + cmd |=3D AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (msg->flags & I2C_M_RECV_LEN) { + dev_dbg(i2c_bus->dev, "smbus read\n"); + } else if ((i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) && + ((i2c_bus->controller_xfer_cnt + 1) =3D=3D msg->len)) { + cmd |=3D CONTROLLER_TRIGGER_LAST_STOP; + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_do_start(struct ast2600_i2c_bus *i2c_bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + + /* send start */ + dev_dbg(i2c_bus->dev, "[%d] %s %d byte%s %s 0x%02x\n", + i2c_bus->msgs_index, str_read_write(msg->flags & I2C_M_RD), + msg->len, str_plural(msg->len), + msg->flags & I2C_M_RD ? "from" : "to", msg->addr); + + if (!i2c_bus->setup_rx || !i2c_bus->setup_tx) + return -EINVAL; + + i2c_bus->controller_xfer_cnt =3D 0; + i2c_bus->buf_index =3D 0; + + if (msg->flags & I2C_M_RD) + return i2c_bus->setup_rx(AST2600_I2CM_START_CMD, i2c_bus); + + return i2c_bus->setup_tx(AST2600_I2CM_START_CMD, i2c_bus); +} + +static int ast2600_i2c_irq_err_to_errno(u32 irq_status) +{ + if (irq_status & AST2600_I2CM_ARBIT_LOSS) + return -EAGAIN; + if (irq_status & (AST2600_I2CM_SDA_DL_TO | AST2600_I2CM_SCL_LOW_TO)) + return -ETIMEDOUT; + if (irq_status & (AST2600_I2CM_ABNORMAL)) + return -EPROTO; + + return 0; +} + +static void ast2600_i2c_controller_packet_irq(struct ast2600_i2c_bus *i2c_= bus, u32 sts) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len; + int i; + + sts &=3D ~AST2600_I2CM_PKT_DONE; + writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR); + switch (sts) { + case AST2600_I2CM_PKT_ERROR: + i2c_bus->cmd_err =3D -EAGAIN; + complete(&i2c_bus->cmd_complete); + break; + case AST2600_I2CM_PKT_ERROR | AST2600_I2CM_TX_NAK: /* a0 fix for issue */ + fallthrough; + case AST2600_I2CM_PKT_ERROR | AST2600_I2CM_TX_NAK | AST2600_I2CM_NORMAL_S= TOP: + i2c_bus->cmd_err =3D -ENXIO; + complete(&i2c_bus->cmd_complete); + break; + case AST2600_I2CM_NORMAL_STOP: + /* write 0 byte only have stop isr */ + i2c_bus->msgs_index++; + if (i2c_bus->msgs_index < i2c_bus->msgs_count) { + if (ast2600_i2c_do_start(i2c_bus)) { + i2c_bus->cmd_err =3D -ENOMEM; + complete(&i2c_bus->cmd_complete); + } + } else { + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } + break; + case AST2600_I2CM_TX_ACK: + case AST2600_I2CM_TX_ACK | AST2600_I2CM_NORMAL_STOP: + if (i2c_bus->mode =3D=3D DMA_MODE) + xfer_len =3D AST2600_I2C_GET_TX_DMA_LEN(readl(i2c_bus->reg_base + + AST2600_I2CM_DMA_LEN_STS)); + else if (i2c_bus->mode =3D=3D BUFF_MODE) + xfer_len =3D AST2600_I2CC_GET_TX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + else + xfer_len =3D 1; + + i2c_bus->controller_xfer_cnt +=3D xfer_len; + + if (i2c_bus->controller_xfer_cnt =3D=3D msg->len) { + i2c_bus->msgs_index++; + if (i2c_bus->msgs_index =3D=3D i2c_bus->msgs_count) { + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } else { + if (ast2600_i2c_do_start(i2c_bus)) { + i2c_bus->cmd_err =3D -ENOMEM; + complete(&i2c_bus->cmd_complete); + } + } + } else { + i2c_bus->setup_tx(0, i2c_bus); + } + break; + case AST2600_I2CM_RX_DONE: + case AST2600_I2CM_RX_DONE | AST2600_I2CM_NORMAL_STOP: + /* do next rx */ + if (i2c_bus->mode =3D=3D DMA_MODE) { + xfer_len =3D AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + + AST2600_I2CM_DMA_LEN_STS)); + memcpy(&msg->buf[i2c_bus->controller_xfer_cnt], + i2c_bus->controller_dma_buf, xfer_len); + } else if (i2c_bus->mode =3D=3D BUFF_MODE) { + xfer_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < xfer_len; i++) + msg->buf[i2c_bus->controller_xfer_cnt + i] =3D + readb(i2c_bus->buf_base + 0x10 + i); + } else { + xfer_len =3D 1; + msg->buf[i2c_bus->controller_xfer_cnt] =3D + AST2600_I2CC_GET_RX_BUFF(readl(i2c_bus->reg_base + + AST2600_I2CC_STS_AND_BUFF)); + } + + if (msg->flags & I2C_M_RECV_LEN) { + u8 recv_len =3D AST2600_I2CC_GET_RX_BUFF(readl(i2c_bus->reg_base + + AST2600_I2CC_STS_AND_BUFF)); + msg->len =3D min_t(unsigned int, recv_len, I2C_SMBUS_BLOCK_MAX); + msg->len +=3D ((msg->flags & I2C_CLIENT_PEC) ? 2 : 1); + msg->flags &=3D ~I2C_M_RECV_LEN; + if (!recv_len) + i2c_bus->controller_xfer_cnt =3D 0; + else + i2c_bus->controller_xfer_cnt =3D 1; + } else { + i2c_bus->controller_xfer_cnt +=3D xfer_len; + } + + if (i2c_bus->controller_xfer_cnt =3D=3D msg->len) { + i2c_bus->msgs_index++; + if (i2c_bus->msgs_index =3D=3D i2c_bus->msgs_count) { + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } else { + if (ast2600_i2c_do_start(i2c_bus)) { + i2c_bus->cmd_err =3D -ENOMEM; + complete(&i2c_bus->cmd_complete); + } + } + } else { + i2c_bus->setup_rx(0, i2c_bus); + } + break; + default: + dev_dbg(i2c_bus->dev, "unhandled sts %x\n", sts); + break; + } +} + +static int ast2600_i2c_controller_irq(struct ast2600_i2c_bus *i2c_bus) +{ + u32 sts =3D readl(i2c_bus->reg_base + AST2600_I2CM_ISR); + u32 ctrl; + + sts &=3D ~AST2600_I2CM_SMBUS_ALERT; + + if (sts & AST2600_I2CM_BUS_RECOVER_FAIL) { + writel(AST2600_I2CM_BUS_RECOVER_FAIL, i2c_bus->reg_base + AST2600_I2CM_I= SR); + ctrl =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + i2c_bus->cmd_err =3D -EPROTO; + complete(&i2c_bus->cmd_complete); + return 1; + } + + if (sts & AST2600_I2CM_BUS_RECOVER) { + writel(AST2600_I2CM_BUS_RECOVER, i2c_bus->reg_base + AST2600_I2CM_ISR); + i2c_bus->cmd_err =3D 0; + complete(&i2c_bus->cmd_complete); + return 1; + } + + i2c_bus->cmd_err =3D ast2600_i2c_irq_err_to_errno(sts); + if (i2c_bus->cmd_err) { + writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR); + complete(&i2c_bus->cmd_complete); + return 1; + } + + if (sts & AST2600_I2CM_PKT_DONE) { + ast2600_i2c_controller_packet_irq(i2c_bus, sts); + return 1; + } + + return 0; +} + +static irqreturn_t ast2600_i2c_bus_irq(int irq, void *dev_id) +{ + struct ast2600_i2c_bus *i2c_bus =3D dev_id; + + return IRQ_RETVAL(ast2600_i2c_controller_irq(i2c_bus)); +} + +static int ast2600_i2c_controller_xfer(struct i2c_adapter *adap, struct i2= c_msg *msgs, int num) +{ + struct ast2600_i2c_bus *i2c_bus =3D i2c_get_adapdata(adap); + unsigned long timeout; + int ret; + + if (!i2c_bus->multi_master && + (readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF) & AST2600_I2CC_= BUS_BUSY_STS)) { + ret =3D ast2600_i2c_recover_bus(i2c_bus); + if (ret) + return ret; + } + + i2c_bus->cmd_err =3D 0; + i2c_bus->msgs =3D msgs; + i2c_bus->msgs_index =3D 0; + i2c_bus->msgs_count =3D num; + reinit_completion(&i2c_bus->cmd_complete); + ret =3D ast2600_i2c_do_start(i2c_bus); + if (ret) + goto controller_out; + timeout =3D wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->= adap.timeout); + if (timeout =3D=3D 0) { + u32 ctrl =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + dev_dbg(i2c_bus->dev, "timeout isr[%x], sts[%x]\n", + readl(i2c_bus->reg_base + AST2600_I2CM_ISR), + readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); + writel(ctrl & ~AST2600_I2CC_MASTER_EN, i2c_bus->reg_base + AST2600_I2CC_= FUN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + /* + * A slave holding SCL low can stall the transfer and trigger + * a master timeout. In multi-master mode, attempt bus recovery + * if the bus is still busy. + */ + if (i2c_bus->multi_master && + (readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF) & + AST2600_I2CC_BUS_BUSY_STS)) + ast2600_i2c_recover_bus(i2c_bus); + ret =3D -ETIMEDOUT; + } else { + ret =3D i2c_bus->cmd_err; + } + + dev_dbg(i2c_bus->dev, "bus%d-m: %d end\n", i2c_bus->adap.nr, i2c_bus->cmd= _err); + +controller_out: + return ret; +} + +static int ast2600_i2c_init(struct ast2600_i2c_bus *i2c_bus) +{ + u32 fun_ctrl =3D AST2600_I2CC_BUS_AUTO_RELEASE | AST2600_I2CC_MASTER_EN; + + /* I2C Reset */ + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + if (!i2c_bus->multi_master) + fun_ctrl |=3D AST2600_I2CC_MULTI_MASTER_DIS; + + /* Enable Controller Mode */ + writel(fun_ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + /* disable target address */ + writel(0, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + + /* Set AC Timing */ + ast2600_i2c_ac_timing_config(i2c_bus); + + if (i2c_bus->dma_available) { + i2c_bus->controller_dma_buf =3D + dmam_alloc_coherent(i2c_bus->dev, AST2600_I2C_DMA_SIZE, + &i2c_bus->controller_dma_addr, GFP_KERNEL); + if (!i2c_bus->controller_dma_buf) + return -ENOMEM; + writel(i2c_bus->controller_dma_addr, + i2c_bus->reg_base + AST2600_I2CM_TX_DMA); + writel(i2c_bus->controller_dma_addr, + i2c_bus->reg_base + AST2600_I2CM_RX_DMA); + } + + /* Clear Interrupt */ + writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CM_ISR); + + return 0; +} + +static u32 ast2600_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm i2c_ast2600_algorithm =3D { + .xfer =3D ast2600_i2c_controller_xfer, + .functionality =3D ast2600_i2c_functionality, +}; + +static void ast2600_i2c_set_xfer_mode(struct ast2600_i2c_bus *i2c_bus, + enum xfer_mode mode) +{ + i2c_bus->mode =3D mode; + + switch (mode) { + case DMA_MODE: + i2c_bus->setup_tx =3D ast2600_i2c_setup_dma_tx; + i2c_bus->setup_rx =3D ast2600_i2c_setup_dma_rx; + break; + case BYTE_MODE: + i2c_bus->setup_tx =3D ast2600_i2c_setup_byte_tx; + i2c_bus->setup_rx =3D ast2600_i2c_setup_byte_rx; + break; + case BUFF_MODE: + default: + i2c_bus->setup_tx =3D ast2600_i2c_setup_buff_tx; + i2c_bus->setup_rx =3D ast2600_i2c_setup_buff_rx; + break; + } +} + +static int ast2600_i2c_xfer_mode_parse(const char *buf, enum xfer_mode *mo= de) +{ + if (sysfs_streq(buf, "byte")) { + *mode =3D BYTE_MODE; + return 0; + } + + if (sysfs_streq(buf, "buffer")) { + *mode =3D BUFF_MODE; + return 0; + } + + if (sysfs_streq(buf, "dma")) { + *mode =3D DMA_MODE; + return 0; + } + + return -EINVAL; +} + +static int ast2600_i2c_xfer_mode_check(struct ast2600_i2c_bus *i2c_bus, + enum xfer_mode mode) +{ + if (mode =3D=3D BUFF_MODE && !i2c_bus->buf_base) + return -EINVAL; + if (mode =3D=3D DMA_MODE && !i2c_bus->dma_available) + return -EINVAL; + return 0; +} + +static const char *ast2600_i2c_xfer_mode_name(enum xfer_mode mode) +{ + switch (mode) { + case BYTE_MODE: + return "byte"; + case DMA_MODE: + return "dma"; + case BUFF_MODE: + default: + return "buffer"; + } +} + +static ssize_t xfer_mode_show(struct device *dev, struct device_attribute = *attr, char *buf) +{ + struct ast2600_i2c_bus *i2c_bus =3D dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", ast2600_i2c_xfer_mode_name(i2c_bus->mode)); +} + +static ssize_t xfer_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ast2600_i2c_bus *i2c_bus =3D dev_get_drvdata(dev); + enum xfer_mode mode; + int ret; + + ret =3D ast2600_i2c_xfer_mode_parse(buf, &mode); + if (ret) + return ret; + + ret =3D ast2600_i2c_xfer_mode_check(i2c_bus, mode); + if (ret) + return ret; + + i2c_lock_bus(&i2c_bus->adap, I2C_LOCK_ROOT_ADAPTER); + ast2600_i2c_set_xfer_mode(i2c_bus, mode); + i2c_unlock_bus(&i2c_bus->adap, I2C_LOCK_ROOT_ADAPTER); + + return count; +} + +static DEVICE_ATTR_RW(xfer_mode); + +static int ast2600_i2c_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct ast2600_i2c_bus *i2c_bus; + void __iomem *buf_base; + struct reset_control *rst; + struct resource *res; + u32 global_ctrl; + int ret; + + if (!device_property_present(dev, "aspeed,global-regs")) + return -ENODEV; + + i2c_bus =3D devm_kzalloc(dev, sizeof(*i2c_bus), GFP_KERNEL); + if (!i2c_bus) + return -ENOMEM; + + i2c_bus->reg_base =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(i2c_bus->reg_base)) + return PTR_ERR(i2c_bus->reg_base); + + rst =3D devm_reset_control_get_shared_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "Missing reset ctrl\n"); + + i2c_bus->global_regs =3D + syscon_regmap_lookup_by_phandle(dev_of_node(dev), "aspeed,global-regs"); + if (IS_ERR(i2c_bus->global_regs)) + return PTR_ERR(i2c_bus->global_regs); + + regmap_read(i2c_bus->global_regs, AST2600_I2CG_CTRL, &global_ctrl); + if ((global_ctrl & AST2600_GLOBAL_INIT) !=3D AST2600_GLOBAL_INIT) { + regmap_write(i2c_bus->global_regs, AST2600_I2CG_CTRL, AST2600_GLOBAL_INI= T); + regmap_write(i2c_bus->global_regs, AST2600_I2CG_CLK_DIV_CTRL, I2CCG_DIV_= CTRL); + } + + i2c_bus->dev =3D dev; + i2c_bus->multi_master =3D device_property_read_bool(dev, "multi-master"); + i2c_bus->dma_available =3D device_property_read_bool(dev, "aspeed,enable-= dma"); + + buf_base =3D devm_platform_get_and_ioremap_resource(pdev, 1, &res); + if (!IS_ERR(buf_base)) { + i2c_bus->buf_base =3D buf_base; + i2c_bus->buf_size =3D resource_size(res) / 2; + } + + enum xfer_mode mode; + + if (i2c_bus->dma_available) + mode =3D DMA_MODE; + else if (i2c_bus->buf_base) + mode =3D BUFF_MODE; + else + mode =3D BYTE_MODE; + + ast2600_i2c_set_xfer_mode(i2c_bus, mode); + + /* + * i2c timeout counter: use base clk4 1Mhz, + * per unit: 1/(1000/1024) =3D 1024us + */ + ret =3D device_property_read_u32(dev, "i2c-scl-clk-low-timeout-us", &i2c_= bus->timeout); + if (!ret) + i2c_bus->timeout =3D DIV_ROUND_UP(i2c_bus->timeout, 1024); + + init_completion(&i2c_bus->cmd_complete); + + i2c_bus->irq =3D platform_get_irq(pdev, 0); + if (i2c_bus->irq < 0) + return i2c_bus->irq; + + platform_set_drvdata(pdev, i2c_bus); + + i2c_bus->clk =3D devm_clk_get(i2c_bus->dev, NULL); + if (IS_ERR(i2c_bus->clk)) + return dev_err_probe(i2c_bus->dev, PTR_ERR(i2c_bus->clk), "Can't get clo= ck\n"); + + i2c_bus->apb_clk =3D clk_get_rate(i2c_bus->clk); + + i2c_parse_fw_timings(i2c_bus->dev, &i2c_bus->timing_info, true); + + /* Initialize the I2C adapter */ + i2c_bus->adap.owner =3D THIS_MODULE; + i2c_bus->adap.algo =3D &i2c_ast2600_algorithm; + i2c_bus->adap.retries =3D 0; + i2c_bus->adap.dev.parent =3D i2c_bus->dev; + device_set_node(&i2c_bus->adap.dev, dev_fwnode(dev)); + i2c_bus->adap.algo_data =3D i2c_bus; + strscpy(i2c_bus->adap.name, pdev->name); + i2c_set_adapdata(&i2c_bus->adap, i2c_bus); + + ret =3D ast2600_i2c_init(i2c_bus); + if (ret < 0) + return dev_err_probe(dev, ret, "Unable to initialize i2c %d\n", ret); + + ret =3D devm_request_irq(dev, i2c_bus->irq, ast2600_i2c_bus_irq, 0, + dev_name(dev), i2c_bus); + if (ret < 0) { + ret =3D dev_err_probe(dev, ret, "Unable to request irq %d\n", + i2c_bus->irq); + goto err; + } + + writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER, + i2c_bus->reg_base + AST2600_I2CM_IER); + + ret =3D sysfs_create_file(&dev->kobj, &dev_attr_xfer_mode.attr); + if (ret) + goto err; + + ret =3D devm_i2c_add_adapter(dev, &i2c_bus->adap); + if (ret) { + sysfs_remove_file(&dev->kobj, &dev_attr_xfer_mode.attr); + goto err; + } + + return 0; + +err: + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); + return ret; +} + +static void ast2600_i2c_remove(struct platform_device *pdev) +{ + struct ast2600_i2c_bus *i2c_bus =3D platform_get_drvdata(pdev); + + sysfs_remove_file(&pdev->dev.kobj, &dev_attr_xfer_mode.attr); + + /* Disable everything. */ + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); +} + +static const struct of_device_id ast2600_i2c_of_match[] =3D { + { .compatible =3D "aspeed,ast2600-i2c-bus" }, + { } +}; +MODULE_DEVICE_TABLE(of, ast2600_i2c_of_match); + +static struct platform_driver ast2600_i2c_driver =3D { + .probe =3D ast2600_i2c_probe, + .remove =3D ast2600_i2c_remove, + .driver =3D { + .name =3D "ast2600-i2c", + .of_match_table =3D ast2600_i2c_of_match, + }, +}; +module_platform_driver(ast2600_i2c_driver); + +MODULE_AUTHOR("Ryan Chen "); +MODULE_DESCRIPTION("ASPEED AST2600 I2C Controller Driver"); +MODULE_LICENSE("GPL"); --=20 2.34.1