From nobody Fri Oct 2 14:02:22 2026 Received: from mail-pl1-f172.google.com (mail-pl1-f172.google.com [209.85.214.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE3C72BF002 for ; Fri, 31 Jul 2026 02:38:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785465513; cv=none; b=EPt34DgkHIlZJLeebQ8Z0Q/JnXPvFFHAJyEwsjknwm0fj6GFTiRHtzxcF0F3CwohY1OQtunpPFN7ur4DkfAyl9v9S+TXRulXjjEy3mgp94QisL4JjBIt75Gtdw5QsszbkBlWszpgMbbs9oiXL3P3j0DwpBbyMBcHuv47+odhJ5c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785465513; c=relaxed/simple; bh=9/LoeI+/IuoG2Uexy61PZsKhk2Qa8plLvXsvIoDhxN0=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=JrLr4hdYjJEDxQFchMlJKD9xOuzlkSEk56sLMRMyUAppysnPGzsz7B41F9kXckYiKlkx/uGOQNW2N0nLhY1b34g57/FuafT5zt+pplnh8Lb7ObkYEUbolMrTEgQQmrkmIxSAMW9KGpcjb3B+iFZfTLpjb9HJhYd18K5oUaaqpEk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=EcHyvEQn; arc=none smtp.client-ip=209.85.214.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="EcHyvEQn" Received: by mail-pl1-f172.google.com with SMTP id d9443c01a7336-2cca0c5799eso3869875ad.0 for ; Thu, 30 Jul 2026 19:38:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785465511; x=1786070311; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=YfzAVera8YPzEZfXN0c8xQO741Yx6s8VOLnFqWs4cSQ=; b=EcHyvEQnDk2BaiKM8f0xaUM6f3BU5RL9XZ+W9N0XC39H4WsB6mCdKaC88EYKas84DK PnGapf8XttTTkBULEogMUnopbfArzR9v056k0Vyy2Kj/OlYsvz/AEOKHToEfDwclYbJt Sjcd1UXd/qF+ITcLe4zAi8mhKgNe28bLr3yfXphL57oPgw+/Eu28L8XW3U00O8wfcjpl CzpulVbk4qoGK7ddGZ9+Vr0WXSjAe9Ml0/mSu85ijdKee6rS+Nm/Rq3znFcHOqjaBUzq YOu3lvchWVHx0h3enthe1SezxXZACiF3tcktwyEgQAn4/8wp4lWZEask9vg36NOwkp95 KsgA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785465511; x=1786070311; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=YfzAVera8YPzEZfXN0c8xQO741Yx6s8VOLnFqWs4cSQ=; b=P3hPsTUMOcYiwv40y4as4Hu87oO/fd30TnW0GAuLUJKCBcKGSIWvX3+vm5krlVRhPO 5qFm6m8WL9i5KsxF5ztAGeegKSlyHM0AfDNZQXl3bwf3ld6tVcqh+TNb8aRfwsGfaHgP x4qg4ZxWN1Tsmtao/6XckC8m5ZAqsgoT0fovtQ8pWWsxAjQ6KsX4z5lz79gMiVANrQiF uZgspUusVUY0uqQLYQDLiv/6u+wZHHb8YOl/mJfcAn/4+9yNwqg14x8bJqUHjnHZufj3 SWvC0Dx45I0vsovKh7oIbrcpzkc+ksVH10TQ8rAnAeAi5obq38kFXcxcYG3L+UOijcxS StPQ== X-Forwarded-Encrypted: i=1; AHgh+RpZOdJ/d8lP59AcPwi7klQYNCRn0H5UIlpckScUjQ6dr1njUw6EjYhAjg/7lW+KPYJXvaxz3EZR/4BOLPQ=@vger.kernel.org X-Gm-Message-State: AOJu0YxA0S5IKUFeejQr4mrEbw9R5BkUYO46u+0Zc+tLEM48NQ4kWM82 xkKktSZ1boVx6hQZAVkyTlNuy5whk/KTO4XBRol3UKxYRqJc8oGO5zLs X-Gm-Gg: AR+sD10FxBPwdaEmt4ODGJ17Cr8TmYwIeWcUnmUgqbZapiCLhaEDSjpFn+/3Ace5Pxj Dm/UBcwB2Ct1F7K6XEqMnz6KOiYgcHTFeTb8HJEFDhthR6gTaimY2elRCFCsjQ8B94wbvnss0F5 MEUEYVfEMDr+P6IOt89fh24OFN6qZcVGJFDS1SkY2iB8xXWit2X4MWjxQwR4r7AgZwrPYMC0wlF wTX8671H8jgz6wdsBn8jy6rvo6pDmLJXnOI19YY0uUNWvYBqOdy40EJgiW2Roch3EVxDO4Lxnbp KWKCGK05luMdrCGjsUZKGOzb/WUUHaNRGSPPuxE7+J9FB7ox7IopyQNGNjIbmJNS8W40Gt8z5kV CNay2/as+xO56xBNWW6MOu977iH8xvtQ7h/8zLSPcbC+OXxyhBUg5rqDievwRSRSgGPW/497lCF Zc7vbKf2QAQpPtT+ecpPjBlHgaSq5VfXWhDeev++uiebwmdUdsWbaWHv6MX2gdkHbW1EYvdlWWP koE2WF4+wMEJ559cA1SGkjJz0pn6bTxKPGxshTCiseyrpRqIFs= X-Received: by 2002:a05:6a21:6f8c:b0:3be:b0b3:3962 with SMTP id adf61e73a8af0-3c91b12935cmr74236637.9.1785465510997; Thu, 30 Jul 2026 19:38:30 -0700 (PDT) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-cbe3808a8d9sm81938a12.7.2026.07.30.19.38.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 30 Jul 2026 19:38:30 -0700 (PDT) From: Chi-Wen Weng To: broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: linux-arm-kernel@lists.infradead.org, linux-spi@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, cwweng@nuvoton.com, cwweng.linux@gmail.com, Krzysztof Kozlowski Subject: [PATCH v5 1/2] dt-bindings: spi: nuvoton,ma35d1-qspi: Add Nuvoton MA35D1 QSPI Date: Fri, 31 Jul 2026 10:38:17 +0800 Message-Id: <20260731023818.1626075-2-cwweng.linux@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260731023818.1626075-1-cwweng.linux@gmail.com> References: <20260731023818.1626075-1-cwweng.linux@gmail.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 Content-Type: text/plain; charset="utf-8" From: Chi-Wen Weng Add a devicetree binding for the Quad SPI controller found in Nuvoton MA35D1 SoCs. The controller supports SPI memory devices such as SPI NOR and SPI NAND flashes. It has one register range, one clock input and one reset line, and supports up to two chip selects. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Chi-Wen Weng --- .../bindings/spi/nuvoton,ma35d1-qspi.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qs= pi.yaml diff --git a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml= b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml new file mode 100644 index 000000000000..377ccf4fb224 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/nuvoton,ma35d1-qspi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton MA35D1 Quad SPI Controller + +maintainers: + - Chi-Wen Weng + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +properties: + compatible: + const: nuvoton,ma35d1-qspi + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + resets: + maxItems: 1 + + num-cs: + minimum: 1 + maximum: 2 + default: 2 + +required: + - compatible + - reg + - clocks + - resets + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells =3D <2>; + #size-cells =3D <2>; + + spi@40680000 { + compatible =3D "nuvoton,ma35d1-qspi"; + reg =3D <0 0x40680000 0 0x100>; + interrupts =3D ; + clocks =3D <&clk QSPI0_GATE>; + resets =3D <&sys MA35D1_RESET_QSPI0>; + #address-cells =3D <1>; + #size-cells =3D <0>; + }; + }; --=20 2.25.1 From nobody Fri Oct 2 14:02:22 2026 Received: from mail-pj1-f48.google.com (mail-pj1-f48.google.com [209.85.216.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1F39A3290D9 for ; Fri, 31 Jul 2026 02:38:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785465518; cv=none; b=VwzHgRpxBxliPQMmErzrlMCUxOgdYF13UWKqbbru5O0W1x4E+L35IvlYe+tR8B+KdpyPhZB0m7tHA9cDKJJOZBZW2Vlu+ZgPzV+aB+hVhbe9fDdXDpNXQZExq8RzmxslJi0QUZLFdN2WXdyb/sVHxCe+uh2oEPX3kXRqIQL69GM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785465518; c=relaxed/simple; bh=+ymkwRuiIMebV8vbtKLSG6IOsEgv9ys6vzeJTFUPJH8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=GQF3RJvEn8mGKEwoZxGdZ/sZk6hohBRC32JiYD4lRoeHE0HkBULr8VrXeLQliIfvSRaed3/V+CsJitNmBvczGW0l4GiR2i/irWMe0uc8u9txKcE+tJW3B4aLOHib3QJzTRHF7cEG8lNO02iZ8CviDfCjzCn7+bYWBlAfO3uTrEs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MpFC6eIG; arc=none smtp.client-ip=209.85.216.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MpFC6eIG" Received: by mail-pj1-f48.google.com with SMTP id 98e67ed59e1d1-38125cebfdaso678334a91.1 for ; Thu, 30 Jul 2026 19:38:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785465515; x=1786070315; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=7L7NxzCoTEtLzlj/84sWyAar8r99rZ+COm/cf+Q7yCA=; b=MpFC6eIGjdQeZmayWM0qEbMP4EA+QEwFxO3at45YaUOBjagbVwZCSbFPR0UcXeSQae hQgclhi5+7Mt0j1ud9wIAi8Oi38OQDCr+otSXlo4gdgjuH3+3QeYqSAwRlhjWFtWdJfs 0Fvp47/OGhUbNvC1kMPkeY3MnTCX5C0iPR1Z9R8ElJqfyy4ConmEsGTxt76nHbHQKO3p GnvbMxlSEz59c/pRoLLCH0b1nM+J1NIYIcCIK0am8YAU6SwG/BYvorUs4JTC/eJfZk8n ehcFQMh6J0edkMJkDSRfSigiwn1TeZoTSSzThxS9wXGFuTy6bkqFXTyvLWjl+X6yXJXK L33A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785465515; x=1786070315; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=7L7NxzCoTEtLzlj/84sWyAar8r99rZ+COm/cf+Q7yCA=; b=GEKG11XCDEgtGkMQ9GkIUyuX3q9r69eTj4Q4Xrm2z9WBAY4+VLdtamKpNK7MRNIdaI CpKJlaypwa89pCaUf1htfQaoQupFt2WZvR0aXxs/cLi4wESNlftKTApORnhWbbNKuu6D ko2dtAv3q5l7yfO2Jm9EBPR3Anbb0MJlMKCbFm3aYCKemqKSw4noF6eUR+JUuHOoLEkX gjAAAxJIsAm+aW5BGPp3xisuZbsAF8qXBFXr4aEhYEsMXMNPwyUm6htgdeILVlsW/qvO Fc8s7TwVBMtoIH5A9RyDVEwvgwAG9Tdo1btnOrLqYLWPnbMYIMy1Ia57ZUZeEFszcJN8 vldA== X-Forwarded-Encrypted: i=1; AHgh+RpfHr18lNQU/zJknW5GHV3pHyEjJErscv5wiaXGJ2FguGsTklWA8yH1IXHMdzdD2s59qYJdwf7CNVEzoak=@vger.kernel.org X-Gm-Message-State: AOJu0Yx0b9eTwMdpITKomdfGzhmMW44XShgKDz8kz+sq7sOXgva6zhWR 1guT3gck+fKg70OSNQQC6DQIWyGTHox3C62ZhCvRHvSLtnBejF0oSXU9 X-Gm-Gg: AR+sD13wTV/acgpym99Z9q/UX6ZkzLAAU4747APfBDl8loJMDNliJGF9Kjc2130Glef 2iAIgAYPOeommz5zFb8FtTFsFhj8SWBm9RyoBe+00BpEtuBk3jSdoflZpIhjgb4k2uwyQnwgDp0 uE/NWftFj7+z4+AdLecDcKM/lvAoHR3eU16zS8R6ehzyvoJ2o1Vbm6WS5hpi+lzlDBUq0AtbnJl 9larokd7Zvw9739sgXIpx0Y0sxdh3l01zjsZ2l5NubKkuywsmk7ZdQ2fHarJtLJek13ZS7pQ6/z RH104TFly3Qsx3kPh0o+zLG0dSRqmkEhtbZ+67Zps77M63fWp/LF1zTNWGk6uEC1XWzGL//brB9 mpoOYkNwj0zphfxkEYzTu/BGsyav3rf9oLRBJH0gx6okI05BbWc98hgmuwILK6FHRg/I5HwEMNH wziBHCCEn1tz3O+zVG8EyWVl7D+zZaMvpW/bba3ZP7gaBBmb7iZxlTeOP/gt2Lx75NKvQuKvwK+ YHDDAf35FUkSDaWFLsRBMDT02mfyWdCbhjOpMwm2US4+7Nj9Ws= X-Received: by 2002:a05:6a20:d43:b0:39c:126c:93b5 with SMTP id adf61e73a8af0-3c91b151e9amr93653637.21.1785465515379; Thu, 30 Jul 2026 19:38:35 -0700 (PDT) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-cbe3808a8d9sm81938a12.7.2026.07.30.19.38.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 30 Jul 2026 19:38:34 -0700 (PDT) From: Chi-Wen Weng To: broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: linux-arm-kernel@lists.infradead.org, linux-spi@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, cwweng@nuvoton.com, cwweng.linux@gmail.com Subject: [PATCH v5 2/2] spi: ma35d1-qspi: Add Nuvoton MA35D1 QSPI controller support Date: Fri, 31 Jul 2026 10:38:18 +0800 Message-Id: <20260731023818.1626075-3-cwweng.linux@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260731023818.1626075-1-cwweng.linux@gmail.com> References: <20260731023818.1626075-1-cwweng.linux@gmail.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 Content-Type: text/plain; charset="utf-8" From: Chi-Wen Weng Add SPI controller driver support for the Quad SPI controller found in Nuvoton MA35D1 SoCs. The controller provides two native chip selects and supports standard SPI and spi-mem operations, including single-bit full-duplex transfers and dual or quad half-duplex transfers. Implement 8-bit PIO transfers, per-operation clock configuration, clock and reset handling, and direct execution of SPI memory operations. Limit individual transfers to 4 KiB and SPI messages to 8 KiB to keep the PIO polling path bounded. Read the optional num-cs property and fall back to the hardware default of two chip selects when it is not present. Active-high chip select and DMA transfers are not supported by this initial implementation. Signed-off-by: Chi-Wen Weng --- drivers/spi/Kconfig | 10 + drivers/spi/Makefile | 1 + drivers/spi/spi-ma35d1-qspi.c | 657 ++++++++++++++++++++++++++++++++++ 3 files changed, 668 insertions(+) create mode 100644 drivers/spi/spi-ma35d1-qspi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b563f49e2197..8b8297ee3f2e 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -694,6 +694,16 @@ config SPI_LJCA This driver can also be built as a module. If so, the module will be called spi-ljca. =20 +config SPI_MA35D1_QSPI + tristate "Nuvoton MA35D1 QSPI controller" + depends on ARCH_MA35 || COMPILE_TEST + help + This enables support for the Quad SPI controller found in + Nuvoton MA35D1 SoCs. + + The controller supports SPI memory devices such as SPI NOR and + SPI NAND flashes in single, dual and quad I/O modes. + config SPI_MESON_SPICC tristate "Amlogic Meson SPICC controller" depends on COMMON_CLK diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9d36190a9884..c5bb0efd108c 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -85,6 +85,7 @@ obj-$(CONFIG_SPI_LOONGSON_CORE) +=3D spi-loongson-core.o obj-$(CONFIG_SPI_LOONGSON_PCI) +=3D spi-loongson-pci.o obj-$(CONFIG_SPI_LOONGSON_PLATFORM) +=3D spi-loongson-plat.o obj-$(CONFIG_SPI_LP8841_RTC) +=3D spi-lp8841-rtc.o +obj-$(CONFIG_SPI_MA35D1_QSPI) +=3D spi-ma35d1-qspi.o obj-$(CONFIG_SPI_MESON_SPICC) +=3D spi-meson-spicc.o obj-$(CONFIG_SPI_MESON_SPIFC) +=3D spi-meson-spifc.o obj-$(CONFIG_SPI_MICROCHIP_CORE_QSPI) +=3D spi-microchip-core-qspi.o diff --git a/drivers/spi/spi-ma35d1-qspi.c b/drivers/spi/spi-ma35d1-qspi.c new file mode 100644 index 000000000000..541d5d72484c --- /dev/null +++ b/drivers/spi/spi-ma35d1-qspi.c @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Nuvoton MA35D1 QSPI controller driver +// +// Copyright (c) 2026 Nuvoton Technology Corp. +// Author: Chi-Wen Weng + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offset definitions */ +#define NUVOTON_QSPI_CTL_OFFSET 0x00 /* Control Register, RW */ +#define NUVOTON_QSPI_CLKDIV_OFFSET 0x04 /* Clock Divider Register, RW */ +#define NUVOTON_QSPI_SSCTL_OFFSET 0x08 /* Slave Select Register, RW */ +#define NUVOTON_QSPI_FIFOCTL_OFFSET 0x10 /* FIFO Control Register, RW */ +#define NUVOTON_QSPI_STATUS_OFFSET 0x14 /* Status Register, RW */ +#define NUVOTON_QSPI_TX_OFFSET 0x20 /* Data Transmit Register, WO */ +#define NUVOTON_QSPI_RX_OFFSET 0x30 /* Data Receive Register, RO */ + +/* QSPI Control Register bit masks */ +#define NUVOTON_QSPI_CTL_QUADIOEN_MASK BIT(22) /* Quad I/O Mode Enable */ +#define NUVOTON_QSPI_CTL_DUALIOEN_MASK BIT(21) /* Dual I/O Mode Enable */ +#define NUVOTON_QSPI_CTL_DATDIR_MASK BIT(20) /* Data Port Direction Contro= l */ +#define NUVOTON_QSPI_CTL_REORDER_MASK BIT(19) /* Byte Reorder Function Ena= ble */ +#define NUVOTON_QSPI_CTL_LSB_MASK BIT(13) /* Send LSB First */ +#define NUVOTON_QSPI_CTL_DWIDTH_MASK GENMASK(12, 8) /* Data Width */ +#define NUVOTON_QSPI_CTL_SUSPITV_MASK GENMASK(7, 4) /* Suspend Interval */ +#define NUVOTON_QSPI_CTL_CLKPOL_MASK BIT(3) /* Clock Polarity */ +#define NUVOTON_QSPI_CTL_TXNEG_MASK BIT(2) /* Transmit on Negative Edge */ +#define NUVOTON_QSPI_CTL_RXNEG_MASK BIT(1) /* Receive on Negative Edge */ +#define NUVOTON_QSPI_CTL_SPIEN_MASK BIT(0) /* QSPI Transfer Control Enable= */ + +/* QSPI Clock Divider Register bit masks */ +#define NUVOTON_QSPI_CLKDIV_MASK GENMASK(8, 0) /* Clock Divider */ + +/* QSPI Slave Select Control Register bit masks */ +#define NUVOTON_QSPI_SSCTL_SS1_MASK BIT(1) /* Slave Selection 1 Control */ +#define NUVOTON_QSPI_SSCTL_SS0_MASK BIT(0) /* Slave Selection 0 Control */ + +/* QSPI FIFO Control Register bit masks */ +#define NUVOTON_QSPI_FIFOCTL_TXRST_MASK BIT(1) /* Transmit Reset */ +#define NUVOTON_QSPI_FIFOCTL_RXRST_MASK BIT(0) /* Receive Reset */ + +/* QSPI Status Register bit masks */ +#define NUVOTON_QSPI_STATUS_TXRXRST_MASK BIT(23) /* TX or RX Reset Status = */ +#define NUVOTON_QSPI_STATUS_TXFULL_MASK BIT(17) /* Transmit FIFO Full */ +#define NUVOTON_QSPI_STATUS_SPIENSTS_MASK BIT(15) /* QSPI Enable Status */ +#define NUVOTON_QSPI_STATUS_RXEMPTY_MASK BIT(8) /* Receive FIFO Empty */ +#define NUVOTON_QSPI_STATUS_BUSY_MASK BIT(0) /* Busy Status */ + +#define NUVOTON_QSPI_MAX_NUM_CS 2 +#define NUVOTON_QSPI_DEFAULT_NUM_CS 2 +#define NUVOTON_QSPI_DEFAULT_BPW 8 +/* Bound PIO operations to avoid long atomic polling loops. */ +#define NUVOTON_QSPI_MAX_TRANSFER_SIZE SZ_4K +#define NUVOTON_QSPI_MAX_MESSAGE_SIZE SZ_8K +#define NUVOTON_QSPI_TIMEOUT_US 10000 + +struct nuvoton_qspi { + void __iomem *regs; + struct clk *clk; + struct device *dev; + + /* Protects read-modify-write accesses to the SSCTL register. */ + spinlock_t ssctl_lock; + u32 speed_hz; +}; + +static u32 nuvoton_qspi_read(struct nuvoton_qspi *qspi, u32 reg) +{ + return readl(qspi->regs + reg); +} + +static void nuvoton_qspi_write(struct nuvoton_qspi *qspi, u32 val, u32 reg) +{ + writel(val, qspi->regs + reg); +} + +static void nuvoton_qspi_update_bits(struct nuvoton_qspi *qspi, u32 reg, + u32 mask, u32 val) +{ + u32 tmp; + + tmp =3D nuvoton_qspi_read(qspi, reg); + tmp &=3D ~mask; + tmp |=3D val & mask; + nuvoton_qspi_write(qspi, tmp, reg); +} + +static int nuvoton_qspi_wait_ready(struct nuvoton_qspi *qspi) +{ + u32 val; + + return readl_poll_timeout(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_BUSY_MASK), + 0, NUVOTON_QSPI_TIMEOUT_US); +} + +static int nuvoton_qspi_reset_fifo(struct nuvoton_qspi *qspi) +{ + u32 val; + + val =3D nuvoton_qspi_read(qspi, NUVOTON_QSPI_FIFOCTL_OFFSET); + val |=3D NUVOTON_QSPI_FIFOCTL_TXRST_MASK | + NUVOTON_QSPI_FIFOCTL_RXRST_MASK; + nuvoton_qspi_write(qspi, val, NUVOTON_QSPI_FIFOCTL_OFFSET); + + /* + * Give the controller a short time to latch the FIFO reset request + * before polling the reset status bit. + */ + udelay(1); + + return readl_poll_timeout_atomic(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_TXRXRST_MASK), + 1, NUVOTON_QSPI_TIMEOUT_US); +} + +static int nuvoton_qspi_set_speed(struct nuvoton_qspi *qspi, u32 speed_hz) +{ + unsigned long clk_rate; + u32 div; + + if (!speed_hz) + return -EINVAL; + + if (qspi->speed_hz =3D=3D speed_hz) + return 0; + + clk_rate =3D clk_get_rate(qspi->clk); + if (!clk_rate) { + dev_err(qspi->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + div =3D DIV_ROUND_UP(clk_rate, speed_hz) - 1; + if (div > FIELD_MAX(NUVOTON_QSPI_CLKDIV_MASK)) { + dev_err(qspi->dev, "unsupported SPI clock %u Hz\n", speed_hz); + return -EINVAL; + } + + nuvoton_qspi_write(qspi, FIELD_PREP(NUVOTON_QSPI_CLKDIV_MASK, div), + NUVOTON_QSPI_CLKDIV_OFFSET); + qspi->speed_hz =3D speed_hz; + + return 0; +} + +static int nuvoton_qspi_set_bits_per_word(struct nuvoton_qspi *qspi, u8 bp= w) +{ + if (bpw !=3D NUVOTON_QSPI_DEFAULT_BPW) + return -EINVAL; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_DWIDTH_MASK | + NUVOTON_QSPI_CTL_REORDER_MASK, + FIELD_PREP(NUVOTON_QSPI_CTL_DWIDTH_MASK, bpw)); + + return 0; +} + +static int nuvoton_qspi_setup_transfer(struct spi_device *spi, + u32 speed_hz, u8 bpw) +{ + struct nuvoton_qspi *qspi =3D spi_controller_get_devdata(spi->controller); + u32 mode =3D spi->mode & SPI_MODE_X_MASK; + u32 ctl =3D 0; + int ret; + + if (!speed_hz) + speed_hz =3D spi->max_speed_hz; + + if (!bpw) + bpw =3D NUVOTON_QSPI_DEFAULT_BPW; + + ret =3D nuvoton_qspi_set_speed(qspi, speed_hz); + if (ret) + return ret; + + ret =3D nuvoton_qspi_set_bits_per_word(qspi, bpw); + if (ret) + return ret; + + if (mode =3D=3D SPI_MODE_0 || mode =3D=3D SPI_MODE_3) + ctl |=3D NUVOTON_QSPI_CTL_TXNEG_MASK; + else + ctl |=3D NUVOTON_QSPI_CTL_RXNEG_MASK; + + if (spi->mode & SPI_CPOL) + ctl |=3D NUVOTON_QSPI_CTL_CLKPOL_MASK; + + if (spi->mode & SPI_LSB_FIRST) + ctl |=3D NUVOTON_QSPI_CTL_LSB_MASK; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_TXNEG_MASK | + NUVOTON_QSPI_CTL_RXNEG_MASK | + NUVOTON_QSPI_CTL_CLKPOL_MASK | + NUVOTON_QSPI_CTL_LSB_MASK, ctl); + + return 0; +} + +static void nuvoton_qspi_set_bus_width(struct nuvoton_qspi *qspi, + unsigned int buswidth, + enum spi_mem_data_dir dir) +{ + u32 ctl =3D 0; + + if (buswidth =3D=3D 4) + ctl |=3D NUVOTON_QSPI_CTL_QUADIOEN_MASK; + else if (buswidth =3D=3D 2) + ctl |=3D NUVOTON_QSPI_CTL_DUALIOEN_MASK; + + if (buswidth > 1 && dir =3D=3D SPI_MEM_DATA_OUT) + ctl |=3D NUVOTON_QSPI_CTL_DATDIR_MASK; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_QUADIOEN_MASK | + NUVOTON_QSPI_CTL_DUALIOEN_MASK | + NUVOTON_QSPI_CTL_DATDIR_MASK, ctl); +} + +static u32 nuvoton_qspi_tx_byte(const void *txbuf, unsigned int idx) +{ + if (!txbuf) + return 0; + + return ((const u8 *)txbuf)[idx]; +} + +static void nuvoton_qspi_rx_byte(void *rxbuf, unsigned int idx, u32 val) +{ + if (rxbuf) + ((u8 *)rxbuf)[idx] =3D val; +} + +static int nuvoton_qspi_wait_tx_not_full(struct nuvoton_qspi *qspi) +{ + u32 val; + + return readl_poll_timeout_atomic(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_TXFULL_MASK), + 0, NUVOTON_QSPI_TIMEOUT_US); +} + +static int nuvoton_qspi_wait_rx_not_empty(struct nuvoton_qspi *qspi) +{ + u32 val; + + return readl_poll_timeout_atomic(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, + val, + !(val & NUVOTON_QSPI_STATUS_RXEMPTY_MASK), + 0, NUVOTON_QSPI_TIMEOUT_US); +} + +static int nuvoton_qspi_txrx(struct nuvoton_qspi *qspi, const void *txbuf, + void *rxbuf, unsigned int len) +{ + unsigned int i; + u32 val; + int ret; + + if (!len) + return 0; + + if (len > NUVOTON_QSPI_MAX_TRANSFER_SIZE) + return -EMSGSIZE; + + ret =3D nuvoton_qspi_reset_fifo(qspi); + if (ret) { + dev_err(qspi->dev, "FIFO reset timed out\n"); + return ret; + } + + /* + * Use conservative byte-by-byte PIO access. This keeps the initial driver + * simple and avoids relying on FIFO threshold interrupts or DMA support. + * + * The MA35D1 QSPI controller pushes one RX FIFO entry for each TX byte in + * single, dual-output and quad-output modes. Drain RX after every TX byte + * and discard the value for TX-only transfers to avoid RX FIFO overflow. + */ + for (i =3D 0; i < len; i++) { + ret =3D nuvoton_qspi_wait_tx_not_full(qspi); + if (ret) { + dev_err(qspi->dev, "TX FIFO full timeout\n"); + return ret; + } + + nuvoton_qspi_write(qspi, nuvoton_qspi_tx_byte(txbuf, i), + NUVOTON_QSPI_TX_OFFSET); + + ret =3D nuvoton_qspi_wait_rx_not_empty(qspi); + if (ret) { + dev_err(qspi->dev, "RX FIFO empty timeout\n"); + return ret; + } + + val =3D nuvoton_qspi_read(qspi, NUVOTON_QSPI_RX_OFFSET); + if (rxbuf) + nuvoton_qspi_rx_byte(rxbuf, i, val); + } + + ret =3D nuvoton_qspi_wait_ready(qspi); + if (ret) + dev_err(qspi->dev, "controller busy timeout\n"); + + return ret; +} + +static int nuvoton_qspi_hw_init(struct nuvoton_qspi *qspi) +{ + u32 val; + int ret; + + ret =3D nuvoton_qspi_set_bits_per_word(qspi, NUVOTON_QSPI_DEFAULT_BPW); + if (ret) + return ret; + + nuvoton_qspi_update_bits(qspi, NUVOTON_QSPI_CTL_OFFSET, + NUVOTON_QSPI_CTL_SUSPITV_MASK | + NUVOTON_QSPI_CTL_TXNEG_MASK | + NUVOTON_QSPI_CTL_RXNEG_MASK | + NUVOTON_QSPI_CTL_CLKPOL_MASK | + NUVOTON_QSPI_CTL_LSB_MASK, + NUVOTON_QSPI_CTL_TXNEG_MASK); + + val =3D nuvoton_qspi_read(qspi, NUVOTON_QSPI_CTL_OFFSET); + nuvoton_qspi_write(qspi, val | NUVOTON_QSPI_CTL_SPIEN_MASK, + NUVOTON_QSPI_CTL_OFFSET); + + ret =3D readl_poll_timeout(qspi->regs + NUVOTON_QSPI_STATUS_OFFSET, val, + (val & NUVOTON_QSPI_STATUS_SPIENSTS_MASK), + 1, NUVOTON_QSPI_TIMEOUT_US); + if (ret) { + dev_err(qspi->dev, "failed to enable controller\n"); + return ret; + } + + ret =3D nuvoton_qspi_reset_fifo(qspi); + if (ret) + dev_err(qspi->dev, "FIFO reset timed out\n"); + + return ret; +} + +static size_t nuvoton_qspi_max_transfer_size(struct spi_device *spi) +{ + return NUVOTON_QSPI_MAX_TRANSFER_SIZE; +} + +static size_t nuvoton_qspi_max_message_size(struct spi_device *spi) +{ + return NUVOTON_QSPI_MAX_MESSAGE_SIZE; +} + +static int nuvoton_qspi_mem_adjust_op_size(struct spi_mem *mem, + struct spi_mem_op *op) +{ + if (op->data.nbytes > NUVOTON_QSPI_MAX_TRANSFER_SIZE) + op->data.nbytes =3D NUVOTON_QSPI_MAX_TRANSFER_SIZE; + + return 0; +} + +static bool nuvoton_qspi_mem_supports_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + if (!spi_mem_default_supports_op(mem, op)) + return false; + + if (op->cmd.buswidth > 4 || op->addr.buswidth > 4 || + op->dummy.buswidth > 4 || op->data.buswidth > 4) + return false; + + if (op->cmd.nbytes !=3D 1) + return false; + + if (op->addr.nbytes > 4) + return false; + + return true; +} + +static void nuvoton_qspi_set_cs_level(struct nuvoton_qspi *qspi, + unsigned int cs, bool assert) +{ + unsigned long flags; + u32 mask; + u32 val; + + switch (cs) { + case 0: + mask =3D NUVOTON_QSPI_SSCTL_SS0_MASK; + break; + case 1: + mask =3D NUVOTON_QSPI_SSCTL_SS1_MASK; + break; + default: + dev_warn(qspi->dev, "invalid chip select %u\n", cs); + return; + } + + spin_lock_irqsave(&qspi->ssctl_lock, flags); + + val =3D nuvoton_qspi_read(qspi, NUVOTON_QSPI_SSCTL_OFFSET); + if (assert) + val |=3D mask; + else + val &=3D ~mask; + nuvoton_qspi_write(qspi, val, NUVOTON_QSPI_SSCTL_OFFSET); + + spin_unlock_irqrestore(&qspi->ssctl_lock, flags); +} + +static void nuvoton_qspi_set_cs(struct spi_device *spi, bool level) +{ + struct nuvoton_qspi *qspi =3D spi_controller_get_devdata(spi->controller); + + /* + * The SPI core passes the physical CS level to ->set_cs(). This + * initial driver only supports active-low native chip selects. + */ + nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), !level); +} + +static void nuvoton_qspi_mem_set_cs(struct spi_device *spi, bool assert) +{ + struct nuvoton_qspi *qspi =3D spi_controller_get_devdata(spi->controller); + + /* The direct spi-mem path passes a logical assertion state. */ + nuvoton_qspi_set_cs_level(qspi, spi_get_chipselect(spi, 0), assert); +} + +static int nuvoton_qspi_mem_exec_op(struct spi_mem *mem, + const struct spi_mem_op *op) +{ + struct spi_device *spi =3D mem->spi; + struct nuvoton_qspi *qspi =3D spi_controller_get_devdata(spi->controller); + u8 opcode =3D op->cmd.opcode; + u8 addr[4]; + int ret; + int i; + + ret =3D nuvoton_qspi_setup_transfer(spi, op->max_freq, + NUVOTON_QSPI_DEFAULT_BPW); + if (ret) + return ret; + + nuvoton_qspi_mem_set_cs(spi, true); + + nuvoton_qspi_set_bus_width(qspi, op->cmd.buswidth, SPI_MEM_DATA_OUT); + ret =3D nuvoton_qspi_txrx(qspi, &opcode, NULL, 1); + if (ret) + goto out_deassert_cs; + + if (op->addr.nbytes) { + for (i =3D 0; i < op->addr.nbytes; i++) + addr[i] =3D op->addr.val >> (8 * (op->addr.nbytes - i - 1)); + + nuvoton_qspi_set_bus_width(qspi, op->addr.buswidth, + SPI_MEM_DATA_OUT); + ret =3D nuvoton_qspi_txrx(qspi, addr, NULL, op->addr.nbytes); + if (ret) + goto out_deassert_cs; + } + + if (op->dummy.nbytes) { + nuvoton_qspi_set_bus_width(qspi, op->dummy.buswidth, + SPI_MEM_DATA_OUT); + ret =3D nuvoton_qspi_txrx(qspi, NULL, NULL, op->dummy.nbytes); + if (ret) + goto out_deassert_cs; + } + + if (op->data.nbytes) { + nuvoton_qspi_set_bus_width(qspi, op->data.buswidth, + op->data.dir); + ret =3D nuvoton_qspi_txrx(qspi, + op->data.dir =3D=3D SPI_MEM_DATA_OUT ? + op->data.buf.out : NULL, + op->data.dir =3D=3D SPI_MEM_DATA_IN ? + op->data.buf.in : NULL, + op->data.nbytes); + } + +out_deassert_cs: + nuvoton_qspi_set_bus_width(qspi, 1, SPI_MEM_DATA_IN); + nuvoton_qspi_mem_set_cs(spi, false); + + return ret; +} + +static const struct spi_controller_mem_ops nuvoton_qspi_mem_ops =3D { + .adjust_op_size =3D nuvoton_qspi_mem_adjust_op_size, + .supports_op =3D nuvoton_qspi_mem_supports_op, + .exec_op =3D nuvoton_qspi_mem_exec_op, +}; + +static const struct spi_controller_mem_caps nuvoton_qspi_mem_caps =3D { + .per_op_freq =3D true, +}; + +static int nuvoton_qspi_transfer_one(struct spi_controller *ctlr, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct nuvoton_qspi *qspi =3D spi_controller_get_devdata(ctlr); + unsigned int tx_nbits =3D xfer->tx_nbits ?: SPI_NBITS_SINGLE; + unsigned int rx_nbits =3D xfer->rx_nbits ?: SPI_NBITS_SINGLE; + enum spi_mem_data_dir dir =3D SPI_MEM_DATA_IN; + unsigned int buswidth =3D 1; + int ret; + + ret =3D nuvoton_qspi_setup_transfer(spi, xfer->speed_hz, + xfer->bits_per_word); + if (ret) + return ret; + + if (xfer->tx_buf && xfer->rx_buf && + (tx_nbits !=3D SPI_NBITS_SINGLE || + rx_nbits !=3D SPI_NBITS_SINGLE)) + return -EOPNOTSUPP; + + if (xfer->tx_buf) { + dir =3D SPI_MEM_DATA_OUT; + + if (tx_nbits =3D=3D SPI_NBITS_QUAD) + buswidth =3D 4; + else if (tx_nbits =3D=3D SPI_NBITS_DUAL) + buswidth =3D 2; + } else if (xfer->rx_buf) { + if (rx_nbits =3D=3D SPI_NBITS_QUAD) + buswidth =3D 4; + else if (rx_nbits =3D=3D SPI_NBITS_DUAL) + buswidth =3D 2; + } + + nuvoton_qspi_set_bus_width(qspi, buswidth, dir); + ret =3D nuvoton_qspi_txrx(qspi, xfer->tx_buf, xfer->rx_buf, + xfer->len); + nuvoton_qspi_set_bus_width(qspi, 1, SPI_MEM_DATA_IN); + + return ret; +} + +static int nuvoton_qspi_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct spi_controller *ctlr; + struct nuvoton_qspi *qspi; + struct reset_control *rst; + u32 num_cs =3D NUVOTON_QSPI_DEFAULT_NUM_CS; + int ret; + + ctlr =3D devm_spi_alloc_host(dev, sizeof(*qspi)); + if (!ctlr) + return -ENOMEM; + + platform_set_drvdata(pdev, ctlr); + + qspi =3D spi_controller_get_devdata(ctlr); + qspi->dev =3D dev; + spin_lock_init(&qspi->ssctl_lock); + + qspi->regs =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(qspi->regs)) + return PTR_ERR(qspi->regs); + + rst =3D devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to get reset\n"); + + qspi->clk =3D devm_clk_get_enabled(dev, NULL); + if (IS_ERR(qspi->clk)) + return dev_err_probe(dev, PTR_ERR(qspi->clk), + "failed to get and enable clock\n"); + + ret =3D reset_control_assert(rst); + if (ret) + return dev_err_probe(dev, ret, "failed to assert reset\n"); + + udelay(2); + + ret =3D reset_control_deassert(rst); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert reset\n"); + + ret =3D device_property_read_u32(dev, "num-cs", &num_cs); + if (ret && ret !=3D -EINVAL) + return dev_err_probe(dev, ret, "failed to read num-cs\n"); + + if (!num_cs || num_cs > NUVOTON_QSPI_MAX_NUM_CS) + return dev_err_probe(dev, -EINVAL, "invalid num-cs %u\n", + num_cs); + + ctlr->num_chipselect =3D num_cs; + ctlr->max_transfer_size =3D nuvoton_qspi_max_transfer_size; + ctlr->max_message_size =3D nuvoton_qspi_max_message_size; + ctlr->mem_ops =3D &nuvoton_qspi_mem_ops; + ctlr->mem_caps =3D &nuvoton_qspi_mem_caps; + ctlr->set_cs =3D nuvoton_qspi_set_cs; + ctlr->transfer_one =3D nuvoton_qspi_transfer_one; + ctlr->bits_per_word_mask =3D SPI_BPW_MASK(8); + ctlr->mode_bits =3D SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | + SPI_RX_DUAL | SPI_TX_DUAL | + SPI_RX_QUAD | SPI_TX_QUAD; + ctlr->dev.of_node =3D dev->of_node; + + ret =3D nuvoton_qspi_hw_init(qspi); + if (ret) + return ret; + + ret =3D devm_spi_register_controller(dev, ctlr); + if (ret) + return dev_err_probe(dev, ret, + "failed to register spi controller\n"); + + return 0; +} + +static const struct of_device_id nuvoton_qspi_of_match[] =3D { + { .compatible =3D "nuvoton,ma35d1-qspi" }, + { } +}; +MODULE_DEVICE_TABLE(of, nuvoton_qspi_of_match); + +static struct platform_driver nuvoton_qspi_driver =3D { + .driver =3D { + .name =3D "ma35d1-qspi", + .of_match_table =3D nuvoton_qspi_of_match, + }, + .probe =3D nuvoton_qspi_probe, +}; +module_platform_driver(nuvoton_qspi_driver); + +MODULE_DESCRIPTION("Nuvoton MA35D1 QSPI controller driver"); +MODULE_AUTHOR("Chi-Wen Weng "); +MODULE_LICENSE("GPL"); --=20 2.25.1