From nobody Sun Feb 8 11:14:55 2026 Received: from Atcsqr.andestech.com (60-248-80-70.hinet-ip.hinet.net [60.248.80.70]) (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 6CF2230FF08; Wed, 10 Dec 2025 09:14:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=60.248.80.70 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765358110; cv=none; b=dXmw1urRcrs62AdMWiPw92mqZv7USmsNcSzSocDSQDJm9JC0BNf95HGAUcj90YYtii0GqgfFxm5TFQYVnGEcPFytFPrBzEAwL7pZG6zQjalzWF5f2BbZ9qRcYtdJ9olYfaExjDMUFBrsJZbZoqwQWouk6SRXXhwYGnjdx1bXOKM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765358110; c=relaxed/simple; bh=xdosuEtAIx5iwzgPzzbm5ReGdZNgXrbWk8BHacZVMLA=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=iYkZGeOhjBVJguj61oflg7E28Tf2yLwO2AMyqCQo7E4um8AkO5V4RBuLO9pSiBQrORg/2Eeqfoy3+l2TdiMWZhToRfLpEExpXdmzJmsbn3aInzkAefN/NBZIPDXyvAIx8Gq9gEJI3RUCnVgTTrdD/otwIIfr3FOluR4llC96JD4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=permerror header.from=andestech.com; spf=pass smtp.mailfrom=andestech.com; arc=none smtp.client-ip=60.248.80.70 Authentication-Results: smtp.subspace.kernel.org; dmarc=permerror header.from=andestech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=andestech.com Received: from Atcsqr.andestech.com (localhost [127.0.0.2] (may be forged)) by Atcsqr.andestech.com with ESMTP id 5BA956NU081295; Wed, 10 Dec 2025 17:05:06 +0800 (+08) (envelope-from cl634@andestech.com) Received: from mail.andestech.com (ATCPCS34.andestech.com [10.0.1.134]) by Atcsqr.andestech.com with ESMTPS id 5BA94kSk080809 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=OK); Wed, 10 Dec 2025 17:04:46 +0800 (+08) (envelope-from cl634@andestech.com) Received: from swlinux02.andestech.com (10.0.15.183) by ATCPCS34.andestech.com (10.0.1.134) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.39; Wed, 10 Dec 2025 17:04:46 +0800 From: CL Wang To: , , , , , CC: , , Subject: [PATCH V2 1/3] dt-bindings: spi: Add support for ATCSPI200 SPI controller Date: Wed, 10 Dec 2025 17:04:28 +0800 Message-ID: <20251210090430.3602380-2-cl634@andestech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20251210090430.3602380-1-cl634@andestech.com> References: <20251210090430.3602380-1-cl634@andestech.com> 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 X-ClientProxiedBy: ATCPCS33.andestech.com (10.0.1.100) To ATCPCS34.andestech.com (10.0.1.134) X-DKIM-Results: atcpcs34.andestech.com; dkim=none; X-DNSRBL: X-SPAM-SOURCE-CHECK: pass X-MAIL: Atcsqr.andestech.com 5BA956NU081295 Document devicetree bindings for the Andes ATCSPI200 SPI controller. Signed-off-by: CL Wang - Dropped the "spi_" prefix from dma-names as suggested. - Updated the DT binding and documented all compatible strings. - Added the "andestech,ae350-spi" compatible string. =20 AE350 is part of the AndeShape=E2=84=A2 platform family and is a commer= cially supported product with a fixed, documented SoC-level architecture (memo= ry map, interrupt topology, and peripheral integration). Although AE350 is often deployed on FPGA boards, the platform behaves as a stable SoC integration rather than a prototype. =20 Upstream Linux already accepts FPGA-based platform-level compatible str= ings for stable SoC-like integrations. For example, the Tensilica FPGA platf= orm uses: compatible =3D "cdns,xtfpga-spi"; =20 Following the same rationale, "andestech,ae350-spi" is proposed as the platform-level compatible string for AE350-based devices. More information about AE350 can be found at: https://www.andestech.com/en/products-solutions/andeshape-platforms/ae3= 50-axi-based-platform-pre-integrated-with-n25f-nx25f-a25-ax25/ --- .../bindings/spi/andestech,qilai-spi.yaml | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/andestech,qilai-s= pi.yaml diff --git a/Documentation/devicetree/bindings/spi/andestech,qilai-spi.yaml= b/Documentation/devicetree/bindings/spi/andestech,qilai-spi.yaml new file mode 100644 index 000000000000..e58e6d675d70 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/andestech,qilai-spi.yaml @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/andestech,qilai-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Andes ATCSPI200 SPI controller + +maintainers: + - CL Wang + +properties: + compatible: + enum: + - andestech,qilai-spi + - andestech,ae350-spi + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + num-cs: + description: Number of chip selects supported + maxItems: 1 + + dmas: + items: + - description: Transmit FIFO DMA channel + - description: Receive FIFO DMA channel + + dma-names: + items: + - const: tx + - const: rx + +patternProperties: + "@[0-9a-f]+$": + type: object + additionalProperties: true + + properties: + spi-rx-bus-width: + enum: [1, 4] + + spi-tx-bus-width: + enum: [1, 4] + +allOf: + - $ref: spi-controller.yaml# + +required: + - compatible + - reg + - clocks + - dmas + - dma-names + +unevaluatedProperties: false + +examples: + - | + soc { + #address-cells =3D <2>; + #size-cells =3D <2>; + + spi@f0b00000 { + compatible =3D "andestech,qilai-spi"; + reg =3D <0x0 0xf0b00000 0x0 0x1000>; + #address-cells =3D <1>; + #size-cells =3D <0>; + clocks =3D <&clk_spi>; + dmas =3D <&dma0 0>, <&dma0 1>; + dma-names =3D "tx", "rx"; + + flash@0 { + compatible =3D "jedec,spi-nor"; + reg =3D <0x0>; + spi-tx-bus-width =3D <0x4>; + spi-rx-bus-width =3D <0x4>; + spi-cpol; + spi-cpha; + }; + }; + }; --=20 2.34.1 From nobody Sun Feb 8 11:14:55 2026 Received: from Atcsqr.andestech.com (60-248-80-70.hinet-ip.hinet.net [60.248.80.70]) (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 E9DFA23BF9F; Wed, 10 Dec 2025 09:05:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=60.248.80.70 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765357508; cv=none; b=ddz5h1RV3byi/w301GsN1s/1iCfQi04C6GBiTY8SfMWUPW5RJF9ien8YBwf27REMh3Itwvx12ocWIyHtLsSBt+cXIht6nFW9kphkcezXteMLuciYKAfm/5LKEKmRKN5sCUssm/Bay82h4ZOuNOGXsN9MkJ6g9nCXhexATEkz4K4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765357508; c=relaxed/simple; bh=hOE3q5de8UsVrrUgA0Z+vpCkixNtpiiX8sgCncApBqc=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=IXOXOuI9Fis2DXx4XCqWRhs9LC7zTpHKqM28d49xPvDLgPsDj26uoZ6qdjEOMVSbdN/1TeUnSM2r4WQutXXPpqQ9iGtofmxz83l12K3QQrSSQR3q7J4cxJKJBSO1yrzxpqh02VODEHgkBPNDmYZr89hceZI3IyHb6+Dj6tB0oJk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=permerror header.from=andestech.com; spf=pass smtp.mailfrom=andestech.com; arc=none smtp.client-ip=60.248.80.70 Authentication-Results: smtp.subspace.kernel.org; dmarc=permerror header.from=andestech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=andestech.com Received: from mail.andestech.com (ATCPCS34.andestech.com [10.0.1.134]) by Atcsqr.andestech.com with ESMTPS id 5BA94rhN080875 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=OK); Wed, 10 Dec 2025 17:04:53 +0800 (+08) (envelope-from cl634@andestech.com) Received: from swlinux02.andestech.com (10.0.15.183) by ATCPCS34.andestech.com (10.0.1.134) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.39; Wed, 10 Dec 2025 17:04:53 +0800 From: CL Wang To: , , , , , CC: , , Subject: [PATCH V2 2/3] spi: atcspi200: Add ATCSPI200 SPI controller driver Date: Wed, 10 Dec 2025 17:04:29 +0800 Message-ID: <20251210090430.3602380-3-cl634@andestech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20251210090430.3602380-1-cl634@andestech.com> References: <20251210090430.3602380-1-cl634@andestech.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: ATCPCS33.andestech.com (10.0.1.100) To ATCPCS34.andestech.com (10.0.1.134) X-DKIM-Results: atcpcs34.andestech.com; dkim=none; X-DNSRBL: X-SPAM-SOURCE-CHECK: pass X-MAIL: Atcsqr.andestech.com 5BA94rhN080875 Content-Type: text/plain; charset="utf-8" Add driver for the Andes ATCSPI200 SPI controller. Signed-off-by: CL Wang --- Changes for v2: - Added missing clock disable/unprepare handling in probe error paths. - Switched to devm_dma_request_chan() for proper DMA channel cleanup. - Dropped the "spi_" prefix from dma-names as suggested. - Added suspend/resume support for power management. - Added the "andestech,ae350-spi" compatible string. --- drivers/spi/Kconfig | 9 + drivers/spi/Makefile | 1 + drivers/spi/spi-atcspi200.c | 680 ++++++++++++++++++++++++++++++++++++ 3 files changed, 690 insertions(+) create mode 100644 drivers/spi/spi-atcspi200.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 5520403896fc..617d3095f2c8 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -136,6 +136,15 @@ config SPI_AR934X This enables support for the SPI controller present on the Qualcomm Atheros AR934X/QCA95XX SoCs. =20 +config SPI_ATCSPI200 + tristate "Andes ATCSPI200 SPI controller" + depends on ARCH_ANDES + help + SPI driver for Andes ATCSPI200 SPI controller. + ATCSPI200 controller supports DMA and PIO modes. When DMA + is not available, the driver automatically falls back to + PIO mode. + config SPI_ATH79 tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver" depends on ATH79 || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 863b628ff1ec..96c346144645 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_SPI_APPLE) +=3D spi-apple.o obj-$(CONFIG_SPI_AR934X) +=3D spi-ar934x.o obj-$(CONFIG_SPI_ARMADA_3700) +=3D spi-armada-3700.o obj-$(CONFIG_SPI_ASPEED_SMC) +=3D spi-aspeed-smc.o +obj-$(CONFIG_SPI_ATCSPI200) +=3D spi-atcspi200.o obj-$(CONFIG_SPI_ATMEL) +=3D spi-atmel.o obj-$(CONFIG_SPI_ATMEL_QUADSPI) +=3D atmel-quadspi.o obj-$(CONFIG_SPI_AT91_USART) +=3D spi-at91-usart.o diff --git a/drivers/spi/spi-atcspi200.c b/drivers/spi/spi-atcspi200.c new file mode 100644 index 000000000000..0af7446642e5 --- /dev/null +++ b/drivers/spi/spi-atcspi200.c @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Andes ATCSPI200 SPI Controller + * + * Copyright (C) 2025 Andes Technology Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define ATCSPI_TRANS_FMT 0x10 /* SPI transfer format register */ +#define ATCSPI_TRANS_CTRL 0x20 /* SPI transfer control register */ +#define ATCSPI_CMD 0x24 /* SPI command register */ +#define ATCSPI_ADDR 0x28 /* SPI address register */ +#define ATCSPI_DATA 0x2C /* SPI data register */ +#define ATCSPI_CTRL 0x30 /* SPI control register */ +#define ATCSPI_STATUS 0x34 /* SPI status register */ +#define ATCSPI_TIMING 0x40 /* SPI interface timing register */ +#define ATCSPI_CONFIG 0x7C /* SPI configuration register */ + +/* Transfer format register */ +#define TRANS_FMT_CPHA BIT(0) +#define TRANS_FMT_CPOL BIT(1) +#define TRANS_FMT_DATA_MERGE_EN BIT(7) +#define TRANS_FMT_DATA_LEN_MASK GENMASK(12, 8) +#define TRANS_FMT_ADDR_LEN_MASK GENMASK(17, 16) +#define TRANS_FMT_DATA_LEN(x) FIELD_PREP(TRANS_FMT_DATA_LEN_MASK, (x) - 1) +#define TRANS_FMT_ADDR_LEN(x) FIELD_PREP(TRANS_FMT_ADDR_LEN_MASK, (x) - 1) + +/* Transfer control register */ +#define TRANS_MODE_MASK GENMASK(27, 24) +#define TRANS_MODE_W_ONLY FIELD_PREP(TRANS_MODE_MASK, 1) +#define TRANS_MODE_R_ONLY FIELD_PREP(TRANS_MODE_MASK, 2) +#define TRANS_MODE_NONE_DATA FIELD_PREP(TRANS_MODE_MASK, 7) +#define TRANS_MODE_DMY_READ FIELD_PREP(TRANS_MODE_MASK, 9) +#define TRANS_FIELD_DECNZ(m, x) ((x) ? FIELD_PREP(m, (x) - 1) : 0) +#define TRANS_RD_TRANS_CNT(x) TRANS_FIELD_DECNZ(GENMASK(8, 0), x) +#define TRANS_DUMMY_CNT(x) TRANS_FIELD_DECNZ(GENMASK(10, 9), x) +#define TRANS_WR_TRANS_CNT(x) TRANS_FIELD_DECNZ(GENMASK(20, 12), x) +#define TRANS_DUAL_QUAD(x) FIELD_PREP(GENMASK(23, 22), (x)) +#define TRANS_ADDR_FMT BIT(28) +#define TRANS_ADDR_EN BIT(29) +#define TRANS_CMD_EN BIT(30) + +/* Control register */ +#define CTRL_SPI_RST BIT(0) +#define CTRL_RX_FIFO_RST BIT(1) +#define CTRL_TX_FIFO_RST BIT(2) +#define CTRL_RX_DMA_EN BIT(3) +#define CTRL_TX_DMA_EN BIT(4) + +/* Status register */ +#define ATCSPI_ACTIVE BIT(0) +#define ATCSPI_RX_EMPTY BIT(14) +#define ATCSPI_TX_FULL BIT(23) + +/* Interface timing setting */ +#define TIMING_SCLK_DIV_MASK GENMASK(7, 0) +#define TIMING_SCLK_DIV_MAX 0xFE + +/* Configuration register */ +#define RXFIFO_SIZE(x) FIELD_GET(GENMASK(3, 0), (x)) +#define TXFIFO_SIZE(x) FIELD_GET(GENMASK(7, 4), (x)) + +/* driver configurations */ +#define ATCSPI_MAX_TRANS_LEN 512 +#define ATCSPI_MAX_SPEED_HZ 50000000 +#define ATCSPI_RDY_TIMEOUT_US 1000000 +#define ATCSPI_XFER_TIMEOUT(n) ((n) * 10) +#define ATCSPI_MAX_CS_NUM 1 +#define ATCSPI_DMA_THRESHOLD 256 +#define ATCSPI_BITS_PER_UINT 8 +#define ATCSPI_DATA_MERGE_EN 1 +#define ATCSPI_DMA_SUPPORT 1 + +/** + * struct atcspi_dev - Andes ATCSPI200 SPI controller private data + * @host: Pointer to the SPI controller structure. + * @mutex_lock: A mutex to protect concurrent access to the controller. + * @dma_completion: A completion to signal the end of a DMA transfer. + * @dev: Pointer to the device structure. + * @regmap: Register map for accessing controller registers. + * @clk: Pointer to the controller's functional clock. + * @dma_addr: The physical address of the SPI data register for DMA. + * @clk_rate: The cached frequency of the functional clock. + * @sclk_rate: The target frequency for the SPI clock (SCLK). + * @txfifo_size: The size of the transmit FIFO in bytes. + * @rxfifo_size: The size of the receive FIFO in bytes. + * @data_merge: A flag indicating if the data merge mode is enabled for + * the current transfer. + * @use_dma: Enable DMA mode if ATCSPI_DMA_SUPPORT is set and DMA is + * successfully configured. + */ +struct atcspi_dev { + struct spi_controller *host; + struct mutex mutex_lock; + struct completion dma_completion; + struct device *dev; + struct regmap *regmap; + struct clk *clk; + dma_addr_t dma_addr; + unsigned int clk_rate; + unsigned int sclk_rate; + unsigned int txfifo_size; + unsigned int rxfifo_size; + bool data_merge; + bool use_dma; +}; + +static int atcspi_wait_fifo_ready(struct atcspi_dev *spi, + enum spi_mem_data_dir dir) +{ + unsigned int val; + unsigned int mask; + int ret; + + mask =3D (dir =3D=3D SPI_MEM_DATA_OUT) ? ATCSPI_TX_FULL : ATCSPI_RX_EMPTY; + ret =3D regmap_read_poll_timeout(spi->regmap, + ATCSPI_STATUS, + val, + !(val & mask), + 0, + ATCSPI_RDY_TIMEOUT_US); + if (ret) + dev_info(spi->dev, "Timed out waiting for FIFO ready\n"); + + return ret; +} + +static int atcspi_xfer_data_poll(struct atcspi_dev *spi, + const struct spi_mem_op *op) +{ + void *rx_buf =3D op->data.buf.in; + const void *tx_buf =3D op->data.buf.out; + unsigned int val; + int trans_bytes =3D op->data.nbytes; + int num_byte; + int ret =3D 0; + + num_byte =3D spi->data_merge ? 4 : 1; + while (trans_bytes) { + if (op->data.dir =3D=3D SPI_MEM_DATA_OUT) { + ret =3D atcspi_wait_fifo_ready(spi, SPI_MEM_DATA_OUT); + if (ret) + return ret; + + if (spi->data_merge) + val =3D *(unsigned int *)tx_buf; + else + val =3D *(unsigned char *)tx_buf; + regmap_write(spi->regmap, ATCSPI_DATA, val); + tx_buf =3D (unsigned char *)tx_buf + num_byte; + } else { + ret =3D atcspi_wait_fifo_ready(spi, SPI_MEM_DATA_IN); + if (ret) + return ret; + + regmap_read(spi->regmap, ATCSPI_DATA, &val); + if (spi->data_merge) + *(unsigned int *)rx_buf =3D val; + else + *(unsigned char *)rx_buf =3D (unsigned char)val; + rx_buf =3D (unsigned char *)rx_buf + num_byte; + } + trans_bytes -=3D num_byte; + } + + return ret; +} + +static void atcspi_set_trans_ctl(struct atcspi_dev *spi, + const struct spi_mem_op *op) +{ + unsigned int tc =3D 0; + + if (op->cmd.nbytes) + tc |=3D TRANS_CMD_EN; + if (op->addr.nbytes) + tc |=3D TRANS_ADDR_EN; + if (op->addr.buswidth > 1) + tc |=3D TRANS_ADDR_FMT; + if (op->data.nbytes) { + tc |=3D TRANS_DUAL_QUAD(ffs(op->data.buswidth) - 1); + if (op->data.dir =3D=3D SPI_MEM_DATA_IN) { + if (op->dummy.nbytes) + tc |=3D TRANS_MODE_DMY_READ | + TRANS_DUMMY_CNT(op->dummy.nbytes); + else + tc |=3D TRANS_MODE_R_ONLY; + tc |=3D TRANS_RD_TRANS_CNT(op->data.nbytes); + } else { + tc |=3D TRANS_MODE_W_ONLY | + TRANS_WR_TRANS_CNT(op->data.nbytes); + } + } else { + tc |=3D TRANS_MODE_NONE_DATA; + } + regmap_write(spi->regmap, ATCSPI_TRANS_CTRL, tc); +} + +static void atcspi_set_trans_fmt(struct atcspi_dev *spi, + const struct spi_mem_op *op) +{ + unsigned int val; + + regmap_read(spi->regmap, ATCSPI_TRANS_FMT, &val); + if (op->data.nbytes) { + if (ATCSPI_DATA_MERGE_EN && ATCSPI_BITS_PER_UINT =3D=3D 8 && + !(op->data.nbytes % 4)) { + val |=3D TRANS_FMT_DATA_MERGE_EN; + spi->data_merge =3D true; + } else { + val &=3D ~TRANS_FMT_DATA_MERGE_EN; + spi->data_merge =3D false; + } + } + + val =3D (val & ~TRANS_FMT_ADDR_LEN_MASK) | + TRANS_FMT_ADDR_LEN(op->addr.nbytes); + regmap_write(spi->regmap, ATCSPI_TRANS_FMT, val); +} + +static void atcspi_prepare_trans(struct atcspi_dev *spi, + const struct spi_mem_op *op) +{ + atcspi_set_trans_fmt(spi, op); + atcspi_set_trans_ctl(spi, op); + if (op->addr.nbytes) + regmap_write(spi->regmap, ATCSPI_ADDR, op->addr.val); + regmap_write(spi->regmap, ATCSPI_CMD, op->cmd.opcode); +} + +static int atcspi_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *o= p) +{ + struct atcspi_dev *spi; + + spi =3D spi_controller_get_devdata(mem->spi->controller); + op->data.nbytes =3D min(op->data.nbytes, ATCSPI_MAX_TRANS_LEN); + + /* DMA needs to be aligned to 4 byte */ + if (spi->use_dma && op->data.nbytes >=3D ATCSPI_DMA_THRESHOLD) + op->data.nbytes =3D ALIGN_DOWN(op->data.nbytes, 4); + + return 0; +} + +static int atcspi_dma_config(struct atcspi_dev *spi, bool is_rx) +{ + struct dma_slave_config conf =3D { 0 }; + struct dma_chan *chan; + + if (is_rx) { + chan =3D spi->host->dma_rx; + conf.direction =3D DMA_DEV_TO_MEM; + conf.src_addr =3D spi->dma_addr; + } else { + chan =3D spi->host->dma_tx; + conf.direction =3D DMA_MEM_TO_DEV; + conf.dst_addr =3D spi->dma_addr; + } + conf.dst_maxburst =3D spi->rxfifo_size / 2; + conf.src_maxburst =3D spi->txfifo_size / 2; + + if (spi->data_merge) { + conf.src_addr_width =3D DMA_SLAVE_BUSWIDTH_4_BYTES; + conf.dst_addr_width =3D DMA_SLAVE_BUSWIDTH_4_BYTES; + } else { + conf.src_addr_width =3D DMA_SLAVE_BUSWIDTH_1_BYTE; + conf.dst_addr_width =3D DMA_SLAVE_BUSWIDTH_1_BYTE; + } + + return dmaengine_slave_config(chan, &conf); +} + +static void atcspi_dma_callback(void *arg) +{ + struct completion *dma_completion =3D arg; + + complete(dma_completion); +} + +static int atcspi_dma_trans(struct atcspi_dev *spi, + const struct spi_mem_op *op) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *dma_ch; + struct sg_table sgt; + enum dma_transfer_direction dma_dir; + dma_cookie_t cookie; + unsigned int ctrl; + int timeout; + int ret; + + regmap_read(spi->regmap, ATCSPI_CTRL, &ctrl); + ctrl |=3D CTRL_TX_DMA_EN | CTRL_RX_DMA_EN; + regmap_write(spi->regmap, ATCSPI_CTRL, ctrl); + if (op->data.dir =3D=3D SPI_MEM_DATA_IN) { + ret =3D atcspi_dma_config(spi, TRUE); + dma_dir =3D DMA_DEV_TO_MEM; + dma_ch =3D spi->host->dma_rx; + } else { + ret =3D atcspi_dma_config(spi, FALSE); + dma_dir =3D DMA_MEM_TO_DEV; + dma_ch =3D spi->host->dma_tx; + } + if (ret) + return ret; + + ret =3D spi_controller_dma_map_mem_op_data(spi->host, op, &sgt); + if (ret) + return ret; + + desc =3D dmaengine_prep_slave_sg(dma_ch, sgt.sgl, sgt.nents, dma_dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + ret =3D -ENOMEM; + goto exit_unmap; + } + + reinit_completion(&spi->dma_completion); + desc->callback =3D atcspi_dma_callback; + desc->callback_param =3D &spi->dma_completion; + cookie =3D dmaengine_submit(desc); + ret =3D dma_submit_error(cookie); + if (ret) + goto exit_unmap; + + dma_async_issue_pending(dma_ch); + timeout =3D msecs_to_jiffies(ATCSPI_XFER_TIMEOUT(op->data.nbytes)); + if (!wait_for_completion_timeout(&spi->dma_completion, timeout)) { + ret =3D -ETIMEDOUT; + dmaengine_terminate_all(dma_ch); + } + +exit_unmap: + spi_controller_dma_unmap_mem_op_data(spi->host, op, &sgt); + + return ret; +} + +static int atcspi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op= *op) +{ + struct spi_device *spi_dev =3D mem->spi; + struct atcspi_dev *spi; + unsigned int val; + int ret; + + spi =3D spi_controller_get_devdata(spi_dev->controller); + mutex_lock(&spi->mutex_lock); + atcspi_prepare_trans(spi, op); + if (op->data.nbytes) { + if (spi->use_dma && op->data.nbytes >=3D ATCSPI_DMA_THRESHOLD) + ret =3D atcspi_dma_trans(spi, op); + else + ret =3D atcspi_xfer_data_poll(spi, op); + if (ret) { + dev_info(spi->dev, "SPI transmission failed\n"); + goto exec_mem_exit; + } + } + + ret =3D regmap_read_poll_timeout(spi->regmap, + ATCSPI_STATUS, + val, + !(val & ATCSPI_ACTIVE), + 0, + ATCSPI_RDY_TIMEOUT_US); + if (ret) + dev_info(spi->dev, "Timed out waiting for ATCSPI_ACTIVE\n"); + +exec_mem_exit: + mutex_unlock(&spi->mutex_lock); + + return ret; +} + +static const struct spi_controller_mem_ops atcspi_mem_ops =3D { + .exec_op =3D atcspi_exec_mem_op, + .adjust_op_size =3D atcspi_adjust_op_size, +}; + +static int atcspi_setup(struct atcspi_dev *spi) +{ + unsigned int ctrl_val; + unsigned int val; + int actual_spi_sclk_f; + int ret; + unsigned char div; + + ctrl_val =3D CTRL_TX_FIFO_RST | CTRL_RX_FIFO_RST | CTRL_SPI_RST; + regmap_write(spi->regmap, ATCSPI_CTRL, ctrl_val); + ret =3D regmap_read_poll_timeout(spi->regmap, + ATCSPI_CTRL, + val, + !(val & ctrl_val), + 0, + ATCSPI_RDY_TIMEOUT_US); + if (ret) + return dev_err_probe(spi->dev, ret, + "Timed out waiting for ATCSPI_CTRL\n"); + + val =3D TRANS_FMT_DATA_LEN(ATCSPI_BITS_PER_UINT) | + TRANS_FMT_CPHA | TRANS_FMT_CPOL; + regmap_write(spi->regmap, ATCSPI_TRANS_FMT, val); + + regmap_read(spi->regmap, ATCSPI_CONFIG, &val); + spi->txfifo_size =3D BIT(TXFIFO_SIZE(val) + 1); + spi->rxfifo_size =3D BIT(RXFIFO_SIZE(val) + 1); + + regmap_read(spi->regmap, ATCSPI_TIMING, &val); + val &=3D ~TIMING_SCLK_DIV_MASK; + + /* + * The SCLK_DIV value 0xFF is special and indicates that the + * SCLK rate should be the same as the SPI clock rate. + */ + if (spi->sclk_rate >=3D spi->clk_rate) { + div =3D TIMING_SCLK_DIV_MASK; + } else { + /* + * The divider value is determined as follows: + * 1. If the divider can generate the exact target frequency, + * use that setting. + * 2. If an exact match is not possible, select the closest + * available setting that is lower than the target frequency. + */ + div =3D (spi->clk_rate + (spi->sclk_rate * 2 - 1)) / + (spi->sclk_rate * 2) - 1; + + /* Check if the actual SPI clock is lower than the target */ + actual_spi_sclk_f =3D spi->clk_rate / ((div + 1) * 2); + if (actual_spi_sclk_f < spi->sclk_rate) + dev_info(spi->dev, + "Clock adjusted %d to %d due to divider limitation", + spi->sclk_rate, actual_spi_sclk_f); + + if (div > TIMING_SCLK_DIV_MAX) + return dev_err_probe(spi->dev, -EINVAL, + "Unsupported SPI clock %d\n", + spi->sclk_rate); + } + val |=3D div; + regmap_write(spi->regmap, ATCSPI_TIMING, val); + + return ret; +} + +static int atcspi_init_resources(struct platform_device *pdev, + struct atcspi_dev *spi, + struct resource **mem_res) +{ + void __iomem *base; + const struct regmap_config atcspi_regmap_cfg =3D { + .name =3D "atcspi", + .reg_bits =3D 32, + .val_bits =3D 32, + .cache_type =3D REGCACHE_NONE, + .reg_stride =3D 4, + .pad_bits =3D 0, + .max_register =3D ATCSPI_CONFIG + }; + + base =3D devm_platform_get_and_ioremap_resource(pdev, 0, mem_res); + if (IS_ERR(base)) + return dev_err_probe(spi->dev, PTR_ERR(base), + "Failed to get ioremap resource\n"); + + spi->regmap =3D devm_regmap_init_mmio(spi->dev, base, + &atcspi_regmap_cfg); + if (IS_ERR(spi->regmap)) + return dev_err_probe(spi->dev, PTR_ERR(spi->regmap), + "Failed to init regmap\n"); + + spi->clk =3D devm_clk_get(spi->dev, NULL); + if (IS_ERR(spi->clk)) + return dev_err_probe(spi->dev, PTR_ERR(spi->clk), + "Failed to get SPI clock\n"); + + spi->sclk_rate =3D ATCSPI_MAX_SPEED_HZ; + return 0; +} + +static int atcspi_configure_dma(struct atcspi_dev *spi) +{ + struct dma_chan *dma_chan; + int ret =3D 0; + + dma_chan =3D devm_dma_request_chan(spi->dev, "rx"); + if (IS_ERR(dma_chan)) { + ret =3D PTR_ERR(dma_chan); + goto err_exit; + } + spi->host->dma_rx =3D dma_chan; + + dma_chan =3D devm_dma_request_chan(spi->dev, "tx"); + if (IS_ERR(dma_chan)) { + ret =3D PTR_ERR(dma_chan); + goto free_rx; + } + spi->host->dma_tx =3D dma_chan; + init_completion(&spi->dma_completion); + + return ret; + +free_rx: + dma_release_channel(spi->host->dma_rx); + spi->host->dma_rx =3D NULL; +err_exit: + return ret; +} + +static int atcspi_enable_clk(struct atcspi_dev *spi) +{ + int ret; + + ret =3D clk_prepare_enable(spi->clk); + if (ret) + return dev_err_probe(spi->dev, ret, + "Failed to enable clock\n"); + + spi->clk_rate =3D clk_get_rate(spi->clk); + if (!spi->clk_rate) + return dev_err_probe(spi->dev, -EINVAL, + "Failed to get SPI clock rate\n"); + + return 0; +} + +static void atcspi_init_controller(struct platform_device *pdev, + struct atcspi_dev *spi, + struct spi_controller *host, + struct resource *mem_res) +{ + /* Get the physical address of the data register for DMA transfers. */ + spi->dma_addr =3D (dma_addr_t)(mem_res->start + ATCSPI_DATA); + + /* Initialize controller properties */ + host->bus_num =3D pdev->id; + host->mode_bits =3D SPI_CPOL | SPI_CPHA | SPI_RX_QUAD | SPI_TX_QUAD; + host->dev.of_node =3D pdev->dev.of_node; + host->num_chipselect =3D ATCSPI_MAX_CS_NUM; + host->mem_ops =3D &atcspi_mem_ops; + host->max_speed_hz =3D spi->sclk_rate; +} + +static int atcspi_probe(struct platform_device *pdev) +{ + struct spi_controller *host; + struct atcspi_dev *spi; + struct resource *mem_res; + int ret; + + host =3D spi_alloc_host(&pdev->dev, sizeof(*spi)); + if (!host) + return -ENOMEM; + + spi =3D spi_controller_get_devdata(host); + spi->host =3D host; + spi->dev =3D &pdev->dev; + dev_set_drvdata(&pdev->dev, host); + + ret =3D atcspi_init_resources(pdev, spi, &mem_res); + if (ret) + goto free_controller; + + ret =3D atcspi_enable_clk(spi); + if (ret) + goto free_controller; + + atcspi_init_controller(pdev, spi, host, mem_res); + + ret =3D atcspi_setup(spi); + if (ret) + goto disable_clk; + + ret =3D devm_spi_register_controller(&pdev->dev, host); + if (ret) { + dev_err_probe(spi->dev, ret, + "Failed to register SPI controller\n"); + goto disable_clk; + } + + spi->use_dma =3D false; + if (ATCSPI_DMA_SUPPORT) { + ret =3D atcspi_configure_dma(spi); + if (ret) + dev_info(spi->dev, + "Failed to init DMA, fallback to PIO mode\n"); + else + spi->use_dma =3D true; + } + mutex_init(&spi->mutex_lock); + + return 0; + +disable_clk: + clk_disable_unprepare(spi->clk); + +free_controller: + spi_controller_put(host); + return ret; +} + +static int atcspi_suspend(struct device *dev) +{ + struct spi_controller *host =3D dev_get_drvdata(dev); + struct atcspi_dev *spi =3D spi_controller_get_devdata(host); + + spi_controller_suspend(host); + + clk_disable_unprepare(spi->clk); + + return 0; +} + +static int atcspi_resume(struct device *dev) +{ + struct spi_controller *host =3D dev_get_drvdata(dev); + struct atcspi_dev *spi =3D spi_controller_get_devdata(host); + int ret; + + ret =3D clk_prepare_enable(spi->clk); + if (ret) + return ret; + + ret =3D atcspi_setup(spi); + if (ret) + goto disable_clk; + + ret =3D spi_controller_resume(host); + if (ret) + goto disable_clk; + + return ret; + +disable_clk: + clk_disable_unprepare(spi->clk); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(atcspi_pm_ops, atcspi_suspend, atcspi_resu= me); + +static const struct of_device_id atcspi_of_match[] =3D { + { .compatible =3D "andestech,qilai-spi", }, + { .compatible =3D "andestech,ae350-spi", }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atcspi_of_match); + +static struct platform_driver atcspi_driver =3D { + .probe =3D atcspi_probe, + .driver =3D { + .name =3D "atcspi200", + .owner =3D THIS_MODULE, + .of_match_table =3D atcspi_of_match, + .pm =3D pm_sleep_ptr(&atcspi_pm_ops) + } +}; +module_platform_driver(atcspi_driver); + +MODULE_AUTHOR("CL Wang "); +MODULE_DESCRIPTION("Andes ATCSPI200 SPI controller driver"); +MODULE_LICENSE("GPL"); --=20 2.34.1 From nobody Sun Feb 8 11:14:55 2026 Received: from Atcsqr.andestech.com (60-248-80-70.hinet-ip.hinet.net [60.248.80.70]) (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 3312930497C; Wed, 10 Dec 2025 09:05:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=60.248.80.70 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765357512; cv=none; b=kvbKAlXYGtMnu/GHg27vz4edJidM+pJVpmGEMX92Y+JFBT0E7BiKoMo1i5QYEIABvM4iSnnWIBW8vwa+A+WVBgs+WZ9fbhnPtBnrBE1/4zHu+YVzYaKQM/HY1NYawCLp/wvTrXzyUTHy67MEQ59+H3yhvnGzCszf2f2EgkIpAec= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1765357512; c=relaxed/simple; bh=1pNuUrCkwQCUCVbTWndpLvdT2wMY8V9sH1g+0NmALTk=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=PRAIO9+HynpdPL9VmuT/URdUeM6dPSNeh83TeBEAydQPApZhMsZHIwDo/y5fs1nh4RNP+f0Ac1k6aXVCNt4M3BVNJHtOtY/YscEkf5SdZdrNcAAGMMKocNCBbIT9w6dvoiwCPsPQhIgZzV8fh5XdulccU1NGXPYY032tvyQseFc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=permerror header.from=andestech.com; spf=pass smtp.mailfrom=andestech.com; arc=none smtp.client-ip=60.248.80.70 Authentication-Results: smtp.subspace.kernel.org; dmarc=permerror header.from=andestech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=andestech.com Received: from mail.andestech.com (ATCPCS34.andestech.com [10.0.1.134]) by Atcsqr.andestech.com with ESMTPS id 5BA950AK081158 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=OK); Wed, 10 Dec 2025 17:05:00 +0800 (+08) (envelope-from cl634@andestech.com) Received: from swlinux02.andestech.com (10.0.15.183) by ATCPCS34.andestech.com (10.0.1.134) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.39; Wed, 10 Dec 2025 17:05:00 +0800 From: CL Wang To: , , , , , CC: , , Subject: [PATCH V2 3/3] MAINTAINERS: Add MAINTAINERS entry for the ATCSPI200 SPI controller driver Date: Wed, 10 Dec 2025 17:04:30 +0800 Message-ID: <20251210090430.3602380-4-cl634@andestech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20251210090430.3602380-1-cl634@andestech.com> References: <20251210090430.3602380-1-cl634@andestech.com> 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 X-ClientProxiedBy: ATCPCS33.andestech.com (10.0.1.100) To ATCPCS34.andestech.com (10.0.1.134) X-DKIM-Results: atcpcs34.andestech.com; dkim=none; X-DNSRBL: X-SPAM-SOURCE-CHECK: pass X-MAIL: Atcsqr.andestech.com 5BA950AK081158 MAINTAINERS: Add entry for the Andes ATCSPI200 SPI controller driver Signed-off-by: CL Wang --- Changes for v2: - Split the MAINTAINERS update into a separate patch. --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 8b39a55b939a..74ded6a79efd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1804,6 +1804,12 @@ S: Supported F: drivers/clk/analogbits/* F: include/linux/clk/analogbits* =20 +ANDES ATCSPI200 SPI DRIVER +M: CL Wang +S: Supported +F: Documentation/devicetree/bindings/spi/andestech,qilai-spi.yaml +F: drivers/spi/spi-atcspi200.c + ANDROID DRIVERS M: Greg Kroah-Hartman M: Arve Hj=C3=B8nnev=C3=A5g --=20 2.34.1