From nobody Thu Oct 2 10:55:21 2025 Received: from mail-io1-f45.google.com (mail-io1-f45.google.com [209.85.166.45]) (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 E72A430E0CC for ; Wed, 17 Sep 2025 22:07:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.166.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758146852; cv=none; b=qE6/823nGDCbKweyRWTwPhQenVBZR9zOjV8DkhWeSHy/Gx1qNYaR7Gim2yvY+mWKxUCCTOy393LSTK8i7vCGY22yzw/lSxNiCyqwKRqHJ4NPcRYkFfzj43QMBFJ70NlJAwvyOvERedm9/xk9x8OQit76Xko2H9YwYLaUboHOFZU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758146852; c=relaxed/simple; bh=NnqiGgPMZUUR3po+CxEQhyl8uMPY80BcoRfsT7bEz7E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=d8H678FNmGQj1Ar7AAH0cSKZ8JxrJ/FTWjrlNrGT638acBKQkjkPHOrgVScU+8H7EgQ4liF/DpBC9Pa+hODiGsls7JL4k6KeywLusXZzObOp8d9DaQzOdqK+cgvTOTTM6LHAUcaoRvXZZDHS4WRHwV64WdffMTZC/TwW44GNMDE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riscstar.com; spf=pass smtp.mailfrom=riscstar.com; dkim=pass (2048-bit key) header.d=riscstar-com.20230601.gappssmtp.com header.i=@riscstar-com.20230601.gappssmtp.com header.b=MGksHvMR; arc=none smtp.client-ip=209.85.166.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riscstar.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=riscstar.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=riscstar-com.20230601.gappssmtp.com header.i=@riscstar-com.20230601.gappssmtp.com header.b="MGksHvMR" Received: by mail-io1-f45.google.com with SMTP id ca18e2360f4ac-893661db432so13600139f.1 for ; Wed, 17 Sep 2025 15:07:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=riscstar-com.20230601.gappssmtp.com; s=20230601; t=1758146850; x=1758751650; 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; bh=O00X8I4EestpaFHM5Nwo1tPeldmi7z2kuF+pQ0jsL/o=; b=MGksHvMRXYIv+srKqyD2r5ZEgMomkB3OXLt3VLtJ4F6fkq3oFQ4RXbbY0Egud7dp7H xjqTUtIngvC6QrIxCD/va9NErx0TaPbDc3AGw/lPTN3EMiqeug7NNrv7ZsF6wTvlOUay ObK5cWAjip+y+z4pjIgDffa8BfdsHanJsc8kz5Oc/xvTVXJinUWa3qq+jFNVOXd23MKZ C9zfuCxTD6/sO0j9Y7OhRXohKgBxpVbDVUqg+FZlx5fDdIttGf7Qn2/RdQsbWiWHzo27 TwmqCtakys7u2XuI5ucvea0SqDse3J1BdkrEHOY5RHmSLehmQuid+/0Ze8Xa+b1zECTX X5gg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758146850; x=1758751650; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=O00X8I4EestpaFHM5Nwo1tPeldmi7z2kuF+pQ0jsL/o=; b=S9h5V6OX0cu4GUoPPsUNiwlzRgIOl20LbI00gztv0HdBS5i4A9KcwsmBGb6iegzugR dugxWvAQR5CLCkXQVm5VZrMYcGVcZBXzLkngT6Nx0J+RJ/9AQwzvEdE5sm+TaWMw8768 Vg3ZywN10lP2MdqsNa5bSUiZtJ7vcz7s4zYVH6aVOCrbpAVmHy4aFZWW4z3w62UYZ0/m q3e7wI0XJeGmvT3embDHp2FTqoaeminMrytWTnw0PdoLC0wVCsWFgEeFqnyobeogboex QmeNV17ixdhTliJ3xsXIq+TjNQallLbVpYpiWQIRUlzZgtjVr42XjirDdRkXDmyuq175 +GlQ== X-Forwarded-Encrypted: i=1; AJvYcCXjvLyteajtXlHXGiNZAfyAmOhQMupdHQbcwyNLdlJszgLXKAy54Omv67b/S5VPUGknTISFtoEc/yw5gq4=@vger.kernel.org X-Gm-Message-State: AOJu0YxZ6B1CNbcUOoyOmd20cbKhdt+FrUet4pHxz+weVZylVFjrTN4X YUOjlTTlJ9kVaE1ZVFyZAvS10TuSIETOIWmEnhSFeGpf/4rYvL6hXJ3yDcKFtJZiF+U= X-Gm-Gg: ASbGncseQkMcvG7UYaoZgLdSyDselIMGaLw31EMC48CZmU5i0NSK73UicJokOqg+OhC pcAlJ1j/wDjVS8TlpjMSR9Fxb0lUssXnuPcNsN+seZtWvB0zGO+kGq/f8c49/ofpuSQzAlT47We im+hLgkqTLXOr4YnP495Qm/iV0oAZ0CBvwpNXFS7QfxAgP0wPZBhdQb+oLXxIJl/gTuS/qSGgyB mUNMGUNGvEbI7XhAxxYNkFaXzHnjhRq9ClxOY2fD2NCsyPhLFBftqjMay21wkYVKGhASIRQvSfE NPbNAa/vgKp6LfWBXUZVmyMZcGjb38tW+1A3bXixU68oGFshJqFOEO1Hj4FKrOM2S8vYRbVmhDM 3XvHS5dgoNCNCDnWPmBjAx+CZDBb1h+3gRF1gf61+J5RTn+D0jA3213QIFedsw4NOvcOGraqbgp +YITzadLII X-Google-Smtp-Source: AGHT+IHLVokmCo/lQggG9vmsqXV4YnaHaWdJqCCztWwsUDldvrgs2sfRdEFI4EmXfG+REFOg9/CmjA== X-Received: by 2002:a05:6e02:218a:b0:3fd:96f6:375c with SMTP id e9e14a558f8ab-4241a54ae45mr48172145ab.28.1758146850031; Wed, 17 Sep 2025 15:07:30 -0700 (PDT) Received: from zippy.localdomain (c-75-72-117-212.hsd1.mn.comcast.net. [75.72.117.212]) by smtp.gmail.com with ESMTPSA id e9e14a558f8ab-4244afa9f6fsm2346335ab.22.2025.09.17.15.07.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 17 Sep 2025 15:07:29 -0700 (PDT) From: Alex Elder To: broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: linux-spi@vger.kernel.org, devicetree@vger.kernel.org, dlan@gentoo.org, paul.walmsley@sifive.com, palmer@dabbelt.com, aou@eecs.berkeley.edu, alex@ghiti.fr, p.zabel@pengutronix.de, spacemit@lists.linux.dev, linux-riscv@lists.infradead.org, linux-kernel@vger.kernel.org Subject: [PATCH 1/3] dt-bindings: spi: add SpacemiT K1 SPI support Date: Wed, 17 Sep 2025 17:07:21 -0500 Message-ID: <20250917220724.288127-2-elder@riscstar.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250917220724.288127-1-elder@riscstar.com> References: <20250917220724.288127-1-elder@riscstar.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" Add support for the SPI controller implemented by the SpacemiT K1 SoC. Signed-off-by: Alex Elder --- .../bindings/spi/spacemit,k1-spi.yaml | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/spi/spacemit,k1-spi.y= aml diff --git a/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml b/D= ocumentation/devicetree/bindings/spi/spacemit,k1-spi.yaml new file mode 100644 index 0000000000000..5abd4fe268da9 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spacemit,k1-spi.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spi/spacemit,k1-spi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SpacemiT K1 SoC Serial Peripheral Interface (SPI) + +maintainers: + - Alex Elder + +description: + The SpacemiT K1 SoC implements a SPI controller that has two 32-entry + FIFOs, for transmit and receive. Details are currently available in + section 18.2.1 of the K1 User Manual, found in the SpacemiT Keystone + K1 Documentation[1]. The controller transfers words using PIO. DMA + transfers are supported as well, if both TX and RX DMA channels are + specified, + + [1] https://developer.spacemit.com/documentation + +allOf: + - $ref: /schemas/spi/spi-controller.yaml# + +properties: + compatible: + enum: + - spacemit,k1-spi + + reg: + maxItems: 1 + + clocks: + items: + - description: Core clock + - description: Bus clock + + clock-names: + items: + - const: core + - const: bus + + resets: + maxItems: 1 + + interrupts: + maxItems: 1 + + dmas: + items: + - description: RX DMA channel + - description: TX DMA channel + + dma-names: + items: + - const: rx + - const: tx + + spacemit,k1-ssp-id: + description: SPI controller number + $ref: /schemas/types.yaml#/definitions/uint32 + +required: + - compatible + - reg + - clocks + - clock-names + - resets + - interrupts + +unevaluatedProperties: false + +examples: + - | + + #include + spi3: spi@d401c000 { + compatible =3D "spacemit,k1-spi"; + reg =3D <0xd401c000 0x30>; + #address-cells =3D <1>; + #size-cells =3D <0>; + clocks =3D <&syscon_apbc CLK_SSP3>, + <&syscon_apbc CLK_SSP3_BUS>; + clock-names =3D "core", + "bus"; + resets =3D <&syscon_apbc RESET_SSP3>; + interrupts-extended =3D <&plic 55>; + spacemit,k1-ssp-id =3D <3>; + dmas =3D <&pdma 20>, + <&pdma 19>; + dma-names =3D "rx", + "tx"; + status =3D "disabled"; + }; --=20 2.48.1 From nobody Thu Oct 2 10:55:21 2025 Received: from mail-il1-f175.google.com (mail-il1-f175.google.com [209.85.166.175]) (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 0E7DB30BBA2 for ; Wed, 17 Sep 2025 22:07:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.166.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758146856; cv=none; b=teRpbcfgFcO4klAtQa9i54fcdBAwxOCP7arCByNnC4EFb3E6RwUBCw6FMG3q2nfXjiedftBoXrA6s68aMt3y8STN7mGDkNtz0251KHyELKhMrStA32BlSLqBcJRh3KUecQ2POm0mfZbsr7J+JoETNdEofjZj6iEfrD9EQUPzt7A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758146856; c=relaxed/simple; bh=UOkAzzQuy3LWzjbSBXNgt6bZvXte9WduzJnLoO7aaVM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uFfNn6NUheC7PdeJxER3qDt/CUf0CksmdMtUpsNt9Q9IoKidjQU6vM6tUgtkgW/2zYRPSfZJCa7lDGxb+zf/P+wc21OhjigxdzKx9de/AEf6szxaQ/zv/7o2lkMlHcgQN/7qY8EHuAbLwnn/8Eu1x0cdQlWCK4e2HxRv5UoE+P8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riscstar.com; spf=pass smtp.mailfrom=riscstar.com; dkim=pass (2048-bit key) header.d=riscstar-com.20230601.gappssmtp.com header.i=@riscstar-com.20230601.gappssmtp.com header.b=PAits0+p; arc=none smtp.client-ip=209.85.166.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riscstar.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=riscstar.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=riscstar-com.20230601.gappssmtp.com header.i=@riscstar-com.20230601.gappssmtp.com header.b="PAits0+p" Received: by mail-il1-f175.google.com with SMTP id e9e14a558f8ab-417661ecbb1so2286445ab.2 for ; Wed, 17 Sep 2025 15:07:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=riscstar-com.20230601.gappssmtp.com; s=20230601; t=1758146852; x=1758751652; 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; bh=EGQtGp62UKeUTIRX/tlGYpYMA2uV9fKED2PtfG5fsQs=; b=PAits0+p4TSyD6XjWUOqxzUf7gAuqsDM4kmU1qYKENVEWuNMmX3YyhKa+BiH77vDwq btwsiWlrnqaCuubLb52Q9prJYVjimJeb5/nLs3WyYHQjahR0JD54ueU0J+Dwno+5qkmS yUkjtQ/ZoTheiMF9R30Bs6f7D7h10X4ucUgRw9A+2PgHfEGZC6eGa01ba8OUaJI5+ZEo C6WUKvuWbbTdp5Auzac0CgY6Pm9PFcKk5/cWPZttXDu5DUFDVPsuZjV09XWvZs11svTI 9+Nd4Zq04cM+8gxXul9wMEprz5XeMYDGxScOQ9pu6ldzxYxLY9dMKmFrdmJT+lFcV5w9 NPGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758146852; x=1758751652; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=EGQtGp62UKeUTIRX/tlGYpYMA2uV9fKED2PtfG5fsQs=; b=aMJHZ2XzVFMQ6JL7DPReaFO00psZXYbkK9X2DZSUxzOBirt8SmcH5vA+OGKj8Naw+A erh5N+9lUV4O6zdWMC1r04isngQsW07tK31Xe3NpcxXfahEb3mRz/5J/Ja2cw9zEWxMf pBT13XqXkaFRrXjb1oXSsTgOSTXQV741iE8tPUdoTQyLe+n4MfgSm7LuyVqklm/OYt8Z MJJDgYDpGPWMrUkjhRoFU8llnuLVzWK989HAU0CO0XbIImng086cTfcU7T0BHSP2DP+5 HWA55QpUwOF5sDqcSXJ2uNmROs/WlVG7/fQx1wK4skJg5anjZGUTlUfwSya6E/ht+8kB mYnA== X-Forwarded-Encrypted: i=1; AJvYcCXhmOaDRQtJnLO5u5Zuk59YsWfgE5ks7eOn0Mb0ywFb3ohNNbVSfyLoiGSZRcqWUNRNNKdfC9b3N2mt2ZQ=@vger.kernel.org X-Gm-Message-State: AOJu0Yywq6UwjtIMciRjP3ZixWR+FjQm5O9z3ZGDG+ZibmLys3rT7Z38 28nmtluPifJNj4VV1ZuJZrgWiQ3eUk2H3S02D3grvKNOCOP2V0pegk8y/gll7659+2Q= X-Gm-Gg: ASbGncuXiHNMSLEGdJ+eWggF3IuxtleiId+NSYg5mxJ5dnSfIx0r+iOEtW2mJt2jh+n HioszFswYh7f713qa4jvaw9eXkktgVRW81ZEq39RsWGshhB5Wlx2ZmDoEQK/m9DdXcqBkoXxsFZ 4Zv7XIJc7SasBfx8DGNmaJDt6WW8A+s6AtmLYC8OWnDIXZnsb4QIfIyxCsvo/0Rs8uehDx/mDe3 1pcuC67q3OYw575w1Z2LLLm8su24pTjtkgfs+jZesIQ7z7cW5Lq+Lu/dqTHfufp3hs0JTMh0MXE PKMbsOofgYjAgEYPepUCDQ3eAHvkQVP8wtGT/XG1Tut3D5bIOQXzYJxIMpu+HaUFDVdAn6sGs2L rfNofjLVHM6FaEsDqS1YQDwXJh8JBN0Igk7iGISbH13KN+68euC0nahO6775KnYqjIreHJSHjDx DapswkBbs/ X-Google-Smtp-Source: AGHT+IHpsvS6iQUY8qyhC3NYaG1C1hgoVeQZuZ83fBJcY+JXzZ+68/cetIxJQgGXojJ/2ewN8CUGbw== X-Received: by 2002:a05:6e02:12e7:b0:415:fe45:3dfe with SMTP id e9e14a558f8ab-4241a4bf8eamr59612765ab.3.1758146851852; Wed, 17 Sep 2025 15:07:31 -0700 (PDT) Received: from zippy.localdomain (c-75-72-117-212.hsd1.mn.comcast.net. [75.72.117.212]) by smtp.gmail.com with ESMTPSA id e9e14a558f8ab-4244afa9f6fsm2346335ab.22.2025.09.17.15.07.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 17 Sep 2025 15:07:31 -0700 (PDT) From: Alex Elder To: broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: linux-spi@vger.kernel.org, devicetree@vger.kernel.org, dlan@gentoo.org, paul.walmsley@sifive.com, palmer@dabbelt.com, aou@eecs.berkeley.edu, alex@ghiti.fr, p.zabel@pengutronix.de, spacemit@lists.linux.dev, linux-riscv@lists.infradead.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/3] spi: spacemit: introduce SpacemiT K1 SPI controller driver Date: Wed, 17 Sep 2025 17:07:22 -0500 Message-ID: <20250917220724.288127-3-elder@riscstar.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250917220724.288127-1-elder@riscstar.com> References: <20250917220724.288127-1-elder@riscstar.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" This patch introduces the driver for the SPI controller found in the SpacemiT K1 SoC. Currently the driver supports master mode only. The SPI hardware implements RX and TX FIFOs, 32 entries each, and supports both PIO and DMA mode transfers. Signed-off-by: Alex Elder --- drivers/spi/Kconfig | 8 + drivers/spi/Makefile | 1 + drivers/spi/spi-spacemit-k1.c | 985 ++++++++++++++++++++++++++++++++++ 3 files changed, 994 insertions(+) create mode 100644 drivers/spi/spi-spacemit-k1.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 82fa5eb3b8684..915a52407a85a 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -577,6 +577,14 @@ config SPI_KSPI2 This driver can also be built as a module. If so, the module will be called spi-kspi2. =20 +config SPI_SPACEMIT_K1 + tristate "K1 SPI Controller" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on OF + default ARCH_SPACEMIT + help + Enable support for the SpacemiT K1 SPI controller. + config SPI_LM70_LLP tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)" depends on PARPORT diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index eefaeca097456..29d55eeb9abb4 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_SPI_INGENIC) +=3D spi-ingenic.o obj-$(CONFIG_SPI_INTEL) +=3D spi-intel.o obj-$(CONFIG_SPI_INTEL_PCI) +=3D spi-intel-pci.o obj-$(CONFIG_SPI_INTEL_PLATFORM) +=3D spi-intel-platform.o +obj-$(CONFIG_SPI_SPACEMIT_K1) +=3D spi-spacemit-k1.o obj-$(CONFIG_SPI_LANTIQ_SSC) +=3D spi-lantiq-ssc.o obj-$(CONFIG_SPI_JCORE) +=3D spi-jcore.o obj-$(CONFIG_SPI_KSPI2) +=3D spi-kspi2.o diff --git a/drivers/spi/spi-spacemit-k1.c b/drivers/spi/spi-spacemit-k1.c new file mode 100644 index 0000000000000..6edf75efe7c68 --- /dev/null +++ b/drivers/spi/spi-spacemit-k1.c @@ -0,0 +1,985 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for SpacemiT K1 SPI controller + * + * Copyright (C) 2025 by RISCstar Solutions Corporation. All rights reser= ved. + * Copyright (c) 2023, spacemit Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internals.h" + +/* This is used if the spi-max-frequency property is not present */ +#define K1_SPI_MAX_SPEED_HZ 25600000 + +/* DMA constraints */ +#define K1_SPI_DMA_ALIGNMENT 64 +#define K1_SPI_MAX_DMA_LEN SZ_512K + +/* SpacemiT K1 SPI Registers */ + +/* SSP Top Control Register */ +#define SSP_TOP_CTRL 0x00 +#define TOP_SSE BIT(0) /* Enable port */ +#define TOP_FRF_MASK GENMASK(2, 1) /* Frame format */ +#define TOP_FRF_MOTOROLA 0 /* Motorola SPI */ +#define TOP_DSS_MASK GENMASK(9, 5) /* Data size (1-32) */ +#define TOP_SPO BIT(10) /* Polarity: 0=3Dlow */ +#define TOP_SPH BIT(11) /* Half-cycle phase */ +#define TOP_LBM BIT(12) /* Loopback mode */ +#define TOP_TRAIL BIT(13) /* Trailing bytes */ +#define TOP_HOLD_FRAME_LOW BIT(14) /* Master mode */ + +/* SSP FIFO Control Register */ +#define SSP_FIFO_CTRL 0x04 +#define FIFO_TFT_MASK GENMASK(4, 0) /* TX FIFO threshold */ +#define FIFO_RFT_MASK GENMASK(9, 5) /* RX FIFO threshold */ +#define FIFO_TSRE BIT(10) /* TX service request */ +#define FIFO_RSRE BIT(11) /* RX service request */ + +/* SSP Interrupt Enable Register */ +#define SSP_INT_EN 0x08 +#define SSP_INT_EN_TINTE BIT(1) /* RX timeout */ +#define SSP_INT_EN_RIE BIT(2) /* RX FIFO */ +#define SSP_INT_EN_TIE BIT(3) /* TX FIFO */ +#define SSP_INT_EN_RIM BIT(4) /* RX FIFO overrun */ +#define SSP_INT_EN_TIM BIT(5) /* TX FIFO underrun */ + +/* SSP Time Out Register */ +#define SSP_TIMEOUT 0x0c +#define SSP_TIMEOUT_MASK GENMASK(23, 0) + +/* SSP Data Register */ +#define SSP_DATAR 0x10 + +/* SSP Status Register */ +#define SSP_STATUS 0x14 +#define SSP_STATUS_BSY BIT(0) /* SPI/I2S busy */ +#define SSP_STATUS_TNF BIT(6) /* TX FIFO not full */ +#define SSP_STATUS_TFL GENMASK(11, 7) /* TX FIFO level */ +#define SSP_STATUS_TUR BIT(12) /* TX FIFO underrun */ +#define SSP_STATUS_RNE BIT(14) /* RX FIFO not empty */ +#define SSP_STATUS_RFL GENMASK(19, 15) /* RX FIFO level */ +#define SSP_STATUS_ROR BIT(20) /* RX FIFO overrun */ + +/* The FIFO sizes and thresholds are the same for RX and TX */ +#define K1_SPI_FIFO_SIZE 32 +#define K1_SPI_THRESH (K1_SPI_FIFO_SIZE / 2) + +struct k1_spi_io { + enum dma_data_direction dir; + struct dma_chan *chan; + void *buf; + unsigned int resid; + u32 nents; + struct sg_table sgt; +}; + +struct k1_spi_driver_data { + struct spi_controller *controller; + struct device *dev; + void __iomem *ioaddr; + unsigned long bus_rate; + struct clk *clk; + unsigned long rate; + u32 rx_timeout; + int irq; + + struct k1_spi_io rx; + struct k1_spi_io tx; + + void *dummy; /* DMA disabled if NULL */ + u32 data_reg_addr; /* DMA address of the data register */ + + struct spi_message *message; /* Current message */ + + /* Current transfer information; not valid if message is null */ + unsigned int len; + u32 bytes; /* Bytes used for bits_per_word */ + bool dma_mapped; + struct completion completion; /* Transfer completion */ +}; + +static bool k1_spi_dma_enabled(struct k1_spi_driver_data *drv_data) +{ + return !!drv_data->dummy; +} + +static bool k1_spi_map_dma_buffer(struct k1_spi_io *io, size_t len, void *= dummy) +{ + struct device *dmadev =3D io->chan->device->dev; + unsigned int nents =3D DIV_ROUND_UP(len, SZ_2K); + struct sg_table *sgt =3D &io->sgt; + void *bufp =3D io->buf ? : dummy; + struct scatterlist *sg; + unsigned int i; + + if (nents !=3D sgt->nents) { + sg_free_table(sgt); + if (sg_alloc_table(sgt, nents, GFP_KERNEL)) + return false; + } + + for_each_sg(sgt->sgl, sg, nents, i) { + size_t bytes =3D min_t(size_t, len, SZ_2K); + + sg_set_buf(sg, bufp, bytes); + if (bufp !=3D dummy) + bufp +=3D bytes; + len -=3D bytes; + } + io->nents =3D dma_map_sg(dmadev, sgt->sgl, nents, io->dir); + + return !!io->nents; +} + +static void k1_spi_unmap_dma_buffer(struct k1_spi_io *io) +{ + struct sg_table *sgt =3D &io->sgt; + + dma_unmap_sg(io->chan->device->dev, sgt->sgl, io->nents, io->dir); + io->nents =3D 0; +} + +static bool k1_spi_map_dma_buffers(struct k1_spi_driver_data *drv_data) +{ + u32 dma_burst_size; + void *dummy; + + if (!k1_spi_dma_enabled(drv_data)) + return false; + + dma_burst_size =3D K1_SPI_THRESH * drv_data->bytes; + + /* Don't bother with DMA if we can't do even a single burst */ + if (drv_data->len < dma_burst_size) + return false; + + /* We won't use DMA if the transfer is too big, either */ + if (drv_data->len > K1_SPI_MAX_DMA_LEN) + return false; + + /* Map both directions for DMA; if either fails, we'll use PIO */ + dummy =3D drv_data->dummy; + if (!k1_spi_map_dma_buffer(&drv_data->rx, drv_data->len, dummy)) + return false; + + if (k1_spi_map_dma_buffer(&drv_data->tx, drv_data->len, dummy)) + return true; /* Success! */ + + /* Failed to map the RX buffer; undo the TX mapping */ + k1_spi_unmap_dma_buffer(&drv_data->rx); + + return false; +} + +static struct dma_async_tx_descriptor * +k1_spi_prepare_dma_io(struct k1_spi_driver_data *drv_data, struct k1_spi_i= o *io) +{ + u32 addr =3D drv_data->data_reg_addr; + struct dma_slave_config cfg =3D { }; + enum dma_transfer_direction dir; + enum dma_slave_buswidth width; + u32 dma_burst_size; + int ret; + + dir =3D io->dir =3D=3D DMA_TO_DEVICE ? DMA_MEM_TO_DEV + : DMA_DEV_TO_MEM; + + width =3D drv_data->bytes =3D=3D 1 ? DMA_SLAVE_BUSWIDTH_1_BYTE : + drv_data->bytes =3D=3D 2 ? DMA_SLAVE_BUSWIDTH_2_BYTES + : DMA_SLAVE_BUSWIDTH_4_BYTES; + + dma_burst_size =3D K1_SPI_THRESH * drv_data->bytes; + + cfg.direction =3D dir; + if (dir =3D=3D DMA_MEM_TO_DEV) { + cfg.dst_addr =3D addr; + cfg.dst_addr_width =3D width; + cfg.dst_maxburst =3D dma_burst_size; + } else { + cfg.src_addr =3D addr; + cfg.src_addr_width =3D width; + cfg.src_maxburst =3D dma_burst_size; + } + + ret =3D dmaengine_slave_config(io->chan, &cfg); + if (ret) + return NULL; + + return dmaengine_prep_slave_sg(io->chan, io->sgt.sgl, io->nents, dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); +} + +/* DMA completion callback */ +static void k1_spi_callback(void *data) +{ + complete(data); +} + +static bool k1_spi_transfer_start_dma(struct k1_spi_driver_data *drv_data) +{ + struct dma_async_tx_descriptor *rx_desc; + struct dma_async_tx_descriptor *tx_desc; + struct device *dev =3D drv_data->dev; + void __iomem *virt; + u32 val; + + rx_desc =3D k1_spi_prepare_dma_io(drv_data, &drv_data->rx); + if (!rx_desc) { + dev_err(dev, "failed to get DMA RX descriptor\n"); + return false; + } + + tx_desc =3D k1_spi_prepare_dma_io(drv_data, &drv_data->tx); + if (!tx_desc) { + dev_err(dev, "failed to get DMA TX descriptor\n"); + return false; + } + + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val |=3D TOP_TRAIL; /* Trailing bytes handled by DMA */ + writel(val, virt); + + virt =3D drv_data->ioaddr + SSP_FIFO_CTRL; + val =3D readl(virt); + val |=3D FIFO_TSRE | FIFO_RSRE; + writel(val, virt); + + /* When RX is complete we also know TX has completed */ + rx_desc->callback =3D k1_spi_callback; + rx_desc->callback_param =3D &drv_data->completion; + + dmaengine_submit(rx_desc); + dmaengine_submit(tx_desc); + + dma_async_issue_pending(drv_data->rx.chan); + dma_async_issue_pending(drv_data->tx.chan); + + return true; +} + +static void k1_spi_transfer_end_dma(struct k1_spi_driver_data *drv_data) +{ + void __iomem *virt; + u32 val; + + virt =3D drv_data->ioaddr + SSP_FIFO_CTRL; + val =3D readl(virt); + val &=3D ~(FIFO_TSRE | FIFO_RSRE); + writel(val, virt); + + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val &=3D ~TOP_TRAIL; /* Trailing bytes handled by the CPU */ + writel(val, virt); + + /* Signal an error if an RX overflow or TX underflow occurred */ + val =3D readl(drv_data->ioaddr + SSP_STATUS); + if (val & (SSP_STATUS_TUR | SSP_STATUS_ROR)) + drv_data->message->status =3D -EIO; +} + +/* Discard any data in the RX FIFO */ +static void k1_spi_flush(struct k1_spi_driver_data *drv_data) +{ + u32 val =3D readl(drv_data->ioaddr + SSP_STATUS); + u32 count; + + /* If there's nothing in the FIFO, we're done */ + if (!(val & SSP_STATUS_RNE)) + return; + + /* Read and discard what's there (one more than what the field says) */ + count =3D FIELD_GET(SSP_STATUS_RFL, val) + 1; + do + (void)readl(drv_data->ioaddr + SSP_DATAR); + while (--count); +} + +/* Set the transfer speed; the SPI core code ensures it is supported */ +static bool k1_spi_set_speed(struct k1_spi_driver_data *drv_data, u32 rate) +{ + struct clk *clk =3D drv_data->clk; + u64 nsec_per_word; + u64 bus_ticks; + + if (clk_set_rate(clk, rate)) + return false; + + drv_data->rate =3D clk_get_rate(clk); + + /* + * Compute the RX FIFO inactivity timeout value that should be used. + * The inactivity timer restarts with each word that lands in the + * FIFO. If two or more "word transfer times" pass without any new + * data in the RX FIFO, we might as well read what's there. + * + * The rate at which words land in the FIFO is determined by the + * word size and the transfer rate. One bit is transferred per + * clock tick, and 8 (or 16 or 32) bits are transferred per word. + * + * So we can get word transfer time (in nanoseconds) from: + * nsec_per_tick =3D NANOHZ_PER_HZ / drv_data->rate; + * ticks_per_word =3D BITS_PER_BYTE * drv_data->bytes; + * We do the divide last for better accuracy. + */ + nsec_per_word =3D NANOHZ_PER_HZ * BITS_PER_BYTE * drv_data->bytes; + nsec_per_word =3D DIV_ROUND_UP(nsec_per_word, drv_data->rate); + + /* + * The timeout (which we'll set to three word transfer times) is + * expressed as a number of APB clock ticks. + * bus_ticks =3D 3 * nsec * (drv_data->bus_rate / NANOHZ_PER_HZ) + */ + bus_ticks =3D 3 * nsec_per_word * drv_data->bus_rate; + drv_data->rx_timeout =3D DIV_ROUND_UP(bus_ticks, NANOHZ_PER_HZ); + + return true; +} + +static void k1_spi_read_word(struct k1_spi_driver_data *drv_data) +{ + struct k1_spi_io *rx =3D &drv_data->rx; + u32 bytes =3D drv_data->bytes; + u32 val; + + val =3D readl(drv_data->ioaddr + SSP_DATAR); + rx->resid -=3D bytes; + + if (!rx->buf) + return; /* Null reader: discard the data */ + + if (bytes =3D=3D 1) + *(u8 *)rx->buf =3D val; + else if (bytes =3D=3D 1) + *(u16 *)rx->buf =3D val; + else + *(u32 *)rx->buf =3D val; + + rx->buf +=3D bytes; +} + +static bool k1_spi_read(struct k1_spi_driver_data *drv_data) +{ + struct k1_spi_io *rx =3D &drv_data->rx; + unsigned int count; + u32 val; + + if (!rx->resid) + return true; /* Nothing more to receive */ + + /* We'll read as many slots in the FIFO as there are available */ + val =3D readl(drv_data->ioaddr + SSP_STATUS); + /* The number of open slots is one more than what's in the field */ + count =3D FIELD_GET(SSP_STATUS_RFL, val) + 1; + + /* A full FIFO count means the FIFO is either full or empty */ + + if (count =3D=3D K1_SPI_FIFO_SIZE) + if (!(val & SSP_STATUS_RNE)) + return false; /* Nothing available to read */ + + count =3D min(count, rx->resid); + while (count--) + k1_spi_read_word(drv_data); + + return !rx->resid; +} + +static void k1_spi_write_word(struct k1_spi_driver_data *drv_data) +{ + struct k1_spi_io *tx =3D &drv_data->tx; + u32 bytes; + u32 val; + + bytes =3D drv_data->bytes; + if (tx->buf) { + if (bytes =3D=3D 1) + val =3D *(u8 *)tx->buf; + else if (bytes =3D=3D 2) + val =3D *(u16 *)tx->buf; + else if (bytes =3D=3D 4) + val =3D *(u32 *)tx->buf; + tx->buf +=3D bytes; + } else { + val =3D 0; /* Null writer; write 1, 2, or 4 zero bytes */ + } + + tx->resid -=3D bytes; + writel(val, drv_data->ioaddr + SSP_DATAR); +} + +static bool k1_spi_write(struct k1_spi_driver_data *drv_data) +{ + struct k1_spi_io *tx =3D &drv_data->tx; + unsigned int count; + u32 val; + + if (!tx->resid) + return true; /* Nothing more to send */ + + /* See how many slots in the TX FIFO are available */ + val =3D readl(drv_data->ioaddr + SSP_STATUS); + count =3D FIELD_GET(SSP_STATUS_TFL, val); + + /* A zero count means the FIFO is either full or empty */ + if (!count) { + if (val & SSP_STATUS_TNF) + count =3D K1_SPI_FIFO_SIZE; + else + return false; /* No room in the FIFO */ + } + + /* + * Limit how much we try to send at a time, to reduce the + * chance the other side can overrun our RX FIFO. + */ + count =3D min3(count, K1_SPI_THRESH, tx->resid); + while (count--) + k1_spi_write_word(drv_data); + + return !tx->resid; +} + +static bool k1_spi_transfer_start(struct k1_spi_driver_data *drv_data, + struct spi_transfer *transfer) +{ + void __iomem *virt; + u32 val; + + /* Bits per word can change on a per-transfer basis */ + drv_data->bytes =3D spi_bpw_to_bytes(transfer->bits_per_word); + + /* Each transfer can also specify a different rate */ + if (!k1_spi_set_speed(drv_data, transfer->speed_hz)) { + dev_err(drv_data->dev, "failed to set transfer speed\n"); + return false; + } + + k1_spi_flush(drv_data); + + /* Record the current transfer information */ + drv_data->rx.buf =3D transfer->rx_buf; + drv_data->rx.resid =3D transfer->len; + drv_data->tx.buf =3D (void *)transfer->tx_buf; + drv_data->tx.resid =3D transfer->len; + drv_data->len =3D transfer->len; + + drv_data->dma_mapped =3D k1_spi_map_dma_buffers(drv_data); + + /* Set the RX timeout period (required for both DMA and PIO) */ + val =3D FIELD_PREP(SSP_TIMEOUT_MASK, drv_data->rx_timeout); + writel(val, drv_data->ioaddr + SSP_TIMEOUT); + + /* Data size is one more than the value in the TOP_DSS field */ + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val |=3D FIELD_PREP(TOP_DSS_MASK, transfer->bits_per_word - 1); + writel(val, virt); + + /* Clear any existing interrupt conditions */ + val =3D readl(drv_data->ioaddr + SSP_STATUS); + writel(val, drv_data->ioaddr + SSP_STATUS); + + /* Enable the hardware */ + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val |=3D TOP_SSE; + writel(val, virt); + + /* DMA transfers are programmmed, then initiated */ + if (drv_data->dma_mapped) + return k1_spi_transfer_start_dma(drv_data); + + /* + * For PIO transfers, interrupts will cause words to get + * transferred. The interrupts will get disabled as the + * transfer completes. We'll write what we can to get + * things started. + */ + (void)k1_spi_write(drv_data); + + val =3D SSP_INT_EN_RIM | SSP_INT_EN_TIM; + val |=3D SSP_INT_EN_TINTE | SSP_INT_EN_RIE | SSP_INT_EN_TIE; + writel(val, drv_data->ioaddr + SSP_INT_EN); + + return true; +} + +static void k1_spi_transfer_end(struct k1_spi_driver_data *drv_data, + struct spi_transfer *transfer) +{ + struct spi_message *message =3D drv_data->message; + void __iomem *virt; + u32 val; + + if (drv_data->dma_mapped) + k1_spi_transfer_end_dma(drv_data); + + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val &=3D ~TOP_SSE; + writel(val, virt); + + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val &=3D ~TOP_DSS_MASK; + writel(val, virt); + + writel(0, drv_data->ioaddr + SSP_TIMEOUT); + + if (drv_data->dma_mapped) { + k1_spi_unmap_dma_buffer(&drv_data->tx); + k1_spi_unmap_dma_buffer(&drv_data->rx); + } + + spi_transfer_delay_exec(transfer); + + if (!message->status) + message->actual_length +=3D drv_data->len; +} + +static void k1_spi_transfer_wait(struct k1_spi_driver_data *drv_data) +{ + struct completion *completion =3D &drv_data->completion; + struct spi_message *message =3D drv_data->message; + unsigned long timeout; + int ret; + + /* Length in bits to be transferred */ + timeout =3D BITS_PER_BYTE * drv_data->bytes * drv_data->len; + /* Time (usec) to transfer that many bits at the current bit rate */ + timeout =3D DIV_ROUND_UP(timeout * MICROHZ_PER_HZ, drv_data->rate); + /* Convert that (+ 25%) to jiffies for the wait call */ + timeout =3D usecs_to_jiffies(5 * timeout / 4); + + ret =3D wait_for_completion_interruptible_timeout(completion, timeout); + if (ret > 0) + return; + + message->status =3D -EIO; + if (ret && drv_data->dma_mapped) { + dmaengine_terminate_sync(drv_data->tx.chan); + dmaengine_terminate_sync(drv_data->rx.chan); + } +} + +static int k1_spi_transfer_one_message(struct spi_controller *host, + struct spi_message *message) +{ + struct k1_spi_driver_data *drv_data =3D spi_controller_get_devdata(host); + struct completion *completion =3D &drv_data->completion; + struct spi_transfer *transfer; + void __iomem *virt; + u32 val; + + drv_data->message =3D message; + + /* Message status starts out successful; set to -EIO on error */ + message->status =3D 0; + + /* Hold frame low to avoid losing transferred data */ + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val |=3D TOP_HOLD_FRAME_LOW; + writel(val, virt); + + list_for_each_entry(transfer, &message->transfers, transfer_list) { + reinit_completion(completion); + + /* Issue the next transfer */ + if (!k1_spi_transfer_start(drv_data, transfer)) { + message->status =3D -EIO; + break; + } + + k1_spi_transfer_wait(drv_data); + + k1_spi_transfer_end(drv_data, transfer); + + /* If an error has occurred, we're done */ + if (message->status) + break; + } + + drv_data->message =3D NULL; + + spi_finalize_current_message(drv_data->controller); + + virt =3D drv_data->ioaddr + SSP_TOP_CTRL; + val =3D readl(virt); + val &=3D ~TOP_HOLD_FRAME_LOW; + writel(val, virt); + + return 0; +} + +/* + * The client can call the setup function multiple times, and each call + * can specify a different SPI mode (and transfer speed). Each transfer + * can specify its own speed though, and the core code ensures each + * transfer's speed is set to something nonzero and supported by both + * the controller and the device). We just set the speed for each + * transfer. + */ +static int k1_spi_setup(struct spi_device *spi) +{ + struct k1_spi_driver_data *drv_data; + u32 val; + + drv_data =3D spi_controller_get_devdata(spi->controller); + + /* + * Configure the message format for this device. We only + * support Motorola SPI format in master mode. + */ + val =3D FIELD_PREP(TOP_FRF_MASK, TOP_FRF_MOTOROLA); + val |=3D TOP_HOLD_FRAME_LOW; /* Master mode */ + + /* Translate the mode into the value used to program the hardware. */ + if (spi->mode & SPI_CPHA) + val |=3D TOP_SPH; /* 1/2 cycle */ + if (spi->mode & SPI_CPOL) + val |=3D TOP_SPO; /* active low */ + if (spi->mode & SPI_LOOP) + val |=3D TOP_LBM; /* enable loopback */ + writel(val, drv_data->ioaddr + SSP_TOP_CTRL); + + return 0; +} + +static void k1_spi_cleanup(struct spi_device *spi) +{ + struct k1_spi_driver_data *drv_data; + + drv_data =3D spi_controller_get_devdata(spi->controller); + + writel(0, drv_data->ioaddr + SSP_TOP_CTRL); +} + +static int k1_spi_dma_setup_io(struct k1_spi_driver_data *drv_data, bool r= x) +{ + const char *name =3D rx ? "rx" : "tx"; + struct device *dev =3D drv_data->dev; + struct dma_chan *chan; + struct k1_spi_io *io; + + chan =3D devm_dma_request_chan(dev, name); + if (IS_ERR(chan)) + return PTR_ERR(chan); + + io =3D rx ? &drv_data->rx : &drv_data->tx; + io->dir =3D rx ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + io->chan =3D chan; + + return 0; +} + +static void k1_spi_dma_cleanup_io(struct k1_spi_driver_data *drv_data, boo= l rx) +{ + struct k1_spi_io *io =3D rx ? &drv_data->rx : &drv_data->tx; + + dmaengine_terminate_sync(io->chan); + sg_free_table(&io->sgt); +} + +static int k1_spi_dma_setup(struct k1_spi_driver_data *drv_data) +{ + struct device *dev =3D drv_data->dev; + int rx_ret; + int tx_ret; + + /* We must get both DMA channels, or neither of them */ + rx_ret =3D k1_spi_dma_setup_io(drv_data, true); + if (rx_ret =3D=3D -EPROBE_DEFER) + return -EPROBE_DEFER; + + tx_ret =3D k1_spi_dma_setup_io(drv_data, false); + + /* If neither is specified, we don't use DMA */ + if (rx_ret =3D=3D -ENODEV && tx_ret =3D=3D -ENODEV) + return 0; + + if (rx_ret || tx_ret) + goto err_cleanup; + + drv_data->dummy =3D devm_kzalloc(dev, SZ_2K, GFP_KERNEL); + if (drv_data->dummy) + return 0; /* Success! */ + + dev_warn(dev, "error allocating DMA dummy buffer; DMA disabled\n"); +err_cleanup: + if (tx_ret) { + if (tx_ret !=3D -EPROBE_DEFER) + dev_err(dev, "error requesting DMA TX DMA channel\n"); + } else { + k1_spi_dma_cleanup_io(drv_data, false); + } + + if (rx_ret) + dev_err(dev, "error requesting DMA RX DMA channel\n"); + else + k1_spi_dma_cleanup_io(drv_data, true); + + if (tx_ret =3D=3D -EPROBE_DEFER) + return -EPROBE_DEFER; + + /* Return success if we don't get the dummy buffer; PIO will be used */ + + return rx_ret ? : tx_ret ? : 0; +} + +static void k1_spi_dma_cleanup(struct device *dev, void *res) +{ + struct k1_spi_driver_data *drv_data =3D res; + + if (k1_spi_dma_enabled(drv_data)) { + k1_spi_dma_cleanup_io(drv_data, false); + k1_spi_dma_cleanup_io(drv_data, true); + } +} + +static int devm_k1_spi_dma_setup(struct k1_spi_driver_data *drv_data) +{ + struct k1_spi_driver_data **ptr; + int ret; + + ptr =3D devres_alloc(k1_spi_dma_cleanup, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret =3D k1_spi_dma_setup(drv_data); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr =3D drv_data; + devres_add(drv_data->dev, ptr); + + return 0; +} + +static const struct of_device_id k1_spi_dt_ids[] =3D { + { .compatible =3D "spacemit,k1-spi", }, + {} +}; +MODULE_DEVICE_TABLE(of, k1_spi_dt_ids); + +static void k1_spi_host_init(struct k1_spi_driver_data *drv_data, int id) +{ + struct device_node *np =3D dev_of_node(drv_data->dev); + struct spi_controller *host =3D drv_data->controller; + struct device *dev =3D drv_data->dev; + u32 bus_num; + int ret; + + host->dev.of_node =3D np; + host->dev.parent =3D drv_data->dev; + if (!of_property_read_u32(np, "spacemit,k1-ssp-id", &bus_num)) + host->bus_num =3D bus_num; + else + host->bus_num =3D id; + host->mode_bits =3D SPI_CPOL | SPI_CPHA | SPI_LOOP; + host->bits_per_word_mask =3D SPI_BPW_RANGE_MASK(4, 32); + host->num_chipselect =3D 1; + + if (k1_spi_dma_enabled(drv_data)) + host->dma_alignment =3D K1_SPI_DMA_ALIGNMENT; + host->cleanup =3D k1_spi_cleanup; + host->setup =3D k1_spi_setup; + host->transfer_one_message =3D k1_spi_transfer_one_message; + + ret =3D of_property_read_u32(np, "spi-max-frequency", &host->max_speed_hz= ); + if (ret < 0) { + if (ret !=3D -EINVAL) + dev_warn(dev, "bad spi-max-frequency, using %u\n", + K1_SPI_MAX_SPEED_HZ); + host->max_speed_hz =3D K1_SPI_MAX_SPEED_HZ; + } +} + +/* Set our registers to a known initial state */ +static void +k1_spi_register_reset(struct k1_spi_driver_data *drv_data, bool initial) +{ + u32 val =3D 0; + + writel(0, drv_data->ioaddr + SSP_TOP_CTRL); + + if (initial) { + /* + * The TX and RX FIFO thresholds are the same no matter + * what the speed or bits per word, so we can just set + * them once. The thresholds are one more than the values + * in the register. + */ + val =3D FIELD_PREP(FIFO_RFT_MASK, K1_SPI_THRESH - 1); + val |=3D FIELD_PREP(FIFO_TFT_MASK, K1_SPI_THRESH - 1); + } + writel(val, drv_data->ioaddr + SSP_FIFO_CTRL); + + writel(0, drv_data->ioaddr + SSP_INT_EN); + writel(0, drv_data->ioaddr + SSP_TIMEOUT); + + /* Clear any pending interrupt conditions */ + val =3D readl(drv_data->ioaddr + SSP_STATUS); + writel(val, drv_data->ioaddr + SSP_STATUS); +} + +static irqreturn_t k1_spi_ssp_isr(int irq, void *dev_id) +{ + struct k1_spi_driver_data *drv_data =3D dev_id; + void __iomem *virt; + bool rx_done; + bool tx_done; + u32 val; + + /* Get status and clear pending interrupts */ + virt =3D drv_data->ioaddr + SSP_STATUS; + val =3D readl(virt); + writel(val, virt); + + if (!drv_data->message) + return IRQ_NONE; + + /* Check for a TX underrun or RX underrun first */ + if (val & (SSP_STATUS_TUR | SSP_STATUS_ROR)) { + /* Disable all interrupts on error */ + writel(0, drv_data->ioaddr + SSP_INT_EN); + + drv_data->message->status =3D -EIO; + complete(&drv_data->completion); + + return IRQ_HANDLED; + } + + /* Drain the RX FIFO first, then transmit what we can */ + rx_done =3D k1_spi_read(drv_data); + tx_done =3D k1_spi_write(drv_data); + + /* Disable interrupts if we're done transferring either direction */ + if (rx_done || tx_done) { + virt =3D drv_data->ioaddr + SSP_INT_EN; + + /* If both are done, disable all interrupts */ + if (rx_done && tx_done) { + val =3D 0; + } else { + val =3D readl(virt); + if (rx_done) + val &=3D ~(SSP_INT_EN_TINTE | SSP_INT_EN_RIE); + if (tx_done) + val &=3D ~SSP_INT_EN_TIE; + } + writel(val, virt); + } + + if (rx_done && tx_done) + complete(&drv_data->completion); + + return IRQ_HANDLED; +} + +static int k1_spi_probe(struct platform_device *pdev) +{ + struct k1_spi_driver_data *drv_data; + struct device *dev =3D &pdev->dev; + struct reset_control *reset; + struct spi_controller *host; + struct resource *iores; + struct clk *clk_bus; + int ret; + + host =3D devm_spi_alloc_host(dev, sizeof(*drv_data)); + if (!host) + return -ENOMEM; + drv_data =3D spi_controller_get_devdata(host); + drv_data->controller =3D host; + platform_set_drvdata(pdev, drv_data); + drv_data->dev =3D dev; + init_completion(&drv_data->completion); + + drv_data->ioaddr =3D devm_platform_get_and_ioremap_resource(pdev, 0, + &iores); + if (IS_ERR(drv_data->ioaddr)) + return dev_err_probe(dev, PTR_ERR(drv_data->ioaddr), + "error mapping memory\n"); + drv_data->data_reg_addr =3D iores->start + SSP_DATAR; + + ret =3D devm_k1_spi_dma_setup(drv_data); + if (ret) + return dev_err_probe(dev, ret, "error setting up DMA\n"); + + k1_spi_host_init(drv_data, pdev->id); + + clk_bus =3D devm_clk_get_enabled(dev, "bus"); + if (IS_ERR(clk_bus)) + return dev_err_probe(dev, PTR_ERR(clk_bus), + "error getting/enabling bus clock\n"); + drv_data->bus_rate =3D clk_get_rate(clk_bus); + + drv_data->clk =3D devm_clk_get_enabled(dev, "core"); + if (IS_ERR(drv_data->clk)) + return dev_err_probe(dev, PTR_ERR(drv_data->clk), + "error getting/enabling core clock\n"); + + reset =3D devm_reset_control_get_exclusive_deasserted(dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), + "error getting/deasserting reset\n"); + + k1_spi_register_reset(drv_data, true); + + drv_data->irq =3D platform_get_irq(pdev, 0); + if (drv_data->irq < 0) + return dev_err_probe(dev, drv_data->irq, "error getting IRQ\n"); + + ret =3D devm_request_irq(dev, drv_data->irq, k1_spi_ssp_isr, + IRQF_SHARED, dev_name(dev), drv_data); + if (ret < 0) + return dev_err_probe(dev, ret, "error requesting IRQ\n"); + + ret =3D devm_spi_register_controller(dev, host); + if (ret) + dev_err(dev, "error registering controller\n"); + + return ret; +} + +static void k1_spi_remove(struct platform_device *pdev) +{ + struct k1_spi_driver_data *drv_data =3D platform_get_drvdata(pdev); + + k1_spi_register_reset(drv_data, false); +} + +static struct platform_driver k1_spi_driver =3D { + .driver =3D { + .name =3D "k1x-spi", + .of_match_table =3D k1_spi_dt_ids, + }, + .probe =3D k1_spi_probe, + .remove =3D k1_spi_remove, +}; + +module_platform_driver(k1_spi_driver); + +MODULE_DESCRIPTION("SpacemiT K1 SPI controller driver"); +MODULE_LICENSE("GPL"); --=20 2.48.1 From nobody Thu Oct 2 10:55:21 2025 Received: from mail-il1-f169.google.com (mail-il1-f169.google.com [209.85.166.169]) (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 21AFB30C118 for ; Wed, 17 Sep 2025 22:07:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.166.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758146856; cv=none; b=uSytzvDOnXL+XDt1sw1hFG7fnj81eo/OMgUx0EMt019E39bVVr51d2zlVlxO3ab23WVXVXftKAsXcGT7mr0t/BE4SyoCJZX+zjC7lTVqwYhiUrcA78Glddt2JlG5zI7HOpx53h32ALfUeGrWx7ZuMc/q0NcwMbnujbS6BuxeVoA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758146856; c=relaxed/simple; bh=LQuphTXldy5NKx8fqzkFlqI6SZIIEj7x/KiY6+KNrTk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SLw6f4SkTm1YZFnVoMeJq8/QosoJBAWpAg1QLib0qwIdwxXWfUyIPgb9Psu8cdbuKRR2yRwyHmb9eGAT3/rwLW7nl7ruN0t5F8jKHGJoP4ycfsEn3gkCj4Rm/6ITA8F/49Wb/LACYUxdxSznpLmtimxtrmSaEtAk3tevUHgZUu4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riscstar.com; spf=pass smtp.mailfrom=riscstar.com; dkim=pass (2048-bit key) header.d=riscstar-com.20230601.gappssmtp.com header.i=@riscstar-com.20230601.gappssmtp.com header.b=NTFYdMyu; arc=none smtp.client-ip=209.85.166.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=riscstar.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=riscstar.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=riscstar-com.20230601.gappssmtp.com header.i=@riscstar-com.20230601.gappssmtp.com header.b="NTFYdMyu" Received: by mail-il1-f169.google.com with SMTP id e9e14a558f8ab-40f9e28b09dso1272085ab.0 for ; Wed, 17 Sep 2025 15:07:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=riscstar-com.20230601.gappssmtp.com; s=20230601; t=1758146853; x=1758751653; 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; bh=plxR1Q0OTlig1zTxHt8JGiXb5CbrsV82EqogeHId9hQ=; b=NTFYdMyumvwM6x0QuZqfXj/8HBW0YAiJ3jCP5NfFhf2fGcF5bgL6tF4GDr+1dDnuS4 TtKnXJmGdzLspKROep+6DrMc8pSxMIXJYCYoqBXUW5bjLd95j8mB3bdbg6DWnEnh/0Oq 6DgETnTjv5PNbCW2QROCIKqk/5YY3bBBG2kMORLG5H8gMXDyN1n6nf8KlACM7bXU01EI rbi1nGtY/kAjdqIHdm3jUORoiYevNnR6UHX9jK5XShjELV7CfgaQ8V/K59V1R4lY99Yk qCf3BCnCM834brXnXL39J1L6fSxZRH3X/pRpjcCJ5Aq4Lpw/dbAE29OLsZeyckMC6+GT eanA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758146853; x=1758751653; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=plxR1Q0OTlig1zTxHt8JGiXb5CbrsV82EqogeHId9hQ=; b=jQWcO1t63VFRJyoXiQbwU/pE3xo6mGbpDaPReL87Rkb2XaZKqRHRj/4BW9d/sUZ2O7 qJJ2e06tiPhPeg9552a1a6zcg35jsvnvkn5y3YbXnI/F7NnamARqVaES98THSExAIof8 1JCRD8s9iNxTQGMr4ufSE5KAQCVJVCgDNxwHQ6Jk5UundEKuR+Q4CduvxcEC+qlJ6W70 LaPmH+HFma1IgCPgXGqUbKOY+wRiYndcuSEUqUCGHcJ4OUI6jgfkRMJhQUyaefjSlW2n gQg6QgSsrko/WG88e/O2577qPMJalGacCLbC4pztOE3ahWbwNkRyV4p3E3xnpN7Gfr8K VgWg== X-Forwarded-Encrypted: i=1; AJvYcCV4Ph9PIsHoZHQMNiVP/h/ecEfFeXkiZv3nbS1HExFiIVTypCCpjRjzem5bgSBfdRU0jQz4GufT7o/hZ2o=@vger.kernel.org X-Gm-Message-State: AOJu0YzyChP3RGmfvzZUBdvdbMJuyH+CWTyfzgidUg9FF8OC3iYgR8XN qrJi1FXTWoOj0EK2xphTKuFfeimgRIoEOsKG7f0dJWgbPOpTMST/74OLbKUZsFRA+e8= X-Gm-Gg: ASbGncv+wGg6xsjJAHcK/1rmx7G5IeYyk+63kTdwnlKzvhozvVYh/sKCq8wnQwB02o/ 51WUDzZKrXBlG81a74mdvMglmdUJGyrmnASk67rewDKCpVMSTCPYKxfuUgw5bMUW0QFdNDS8WC7 LB5Zi5bWriQGQXABrV639AGXkLPntqhkk7d8ySazCiDAxZF+rimViMumRfoqEiIweain6emu01J CtOb0W4ny6rZjiIJyBe8GTfTT7EGCR176r0yMeAC7cCPMfmY6xQx7setTV1ZYdeQSx6PpQdx6rQ 5vZ23O+iURxL93mIa7xiJedl8cjOQO5yNwiD6LJb78HpkQTPZG2YdfZP9PnirALvU2AhHluNM88 E4Ee04QLy1hTGogQtowrgyy2b1gg09aS9iQniAC75mzdQ7tn73uCwJi1ZCxZod0Gv4MZtddCuif P8hdaOsjbl X-Google-Smtp-Source: AGHT+IEhEgpu/y6hcFx432FAALiFyoyiIEe7VNM4oad62y0XdESrMbIYe4L7CYQ1KsF8cTepL/COMg== X-Received: by 2002:a05:6e02:164b:b0:3f0:7ad2:2bf5 with SMTP id e9e14a558f8ab-4241a4e1040mr57294085ab.12.1758146853240; Wed, 17 Sep 2025 15:07:33 -0700 (PDT) Received: from zippy.localdomain (c-75-72-117-212.hsd1.mn.comcast.net. [75.72.117.212]) by smtp.gmail.com with ESMTPSA id e9e14a558f8ab-4244afa9f6fsm2346335ab.22.2025.09.17.15.07.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 17 Sep 2025 15:07:32 -0700 (PDT) From: Alex Elder To: broonie@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: linux-spi@vger.kernel.org, devicetree@vger.kernel.org, dlan@gentoo.org, paul.walmsley@sifive.com, palmer@dabbelt.com, aou@eecs.berkeley.edu, alex@ghiti.fr, p.zabel@pengutronix.de, spacemit@lists.linux.dev, linux-riscv@lists.infradead.org, linux-kernel@vger.kernel.org Subject: [PATCH 3/3] riscv: dts: spacemit: define a SPI controller node Date: Wed, 17 Sep 2025 17:07:23 -0500 Message-ID: <20250917220724.288127-4-elder@riscstar.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250917220724.288127-1-elder@riscstar.com> References: <20250917220724.288127-1-elder@riscstar.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" Define a node for the fourth SoC SPI controller (number 3) on the SpacemiT K1 SoC. Enable it on the Banana Pi BPI-F3 board, which exposes this feature via its GPIO block: GPIO PIN 19: MOSI GPIO PIN 21: MISO GPIO PIN 23: SCLK GPIO PIN 24: SS (inverted) Define pincontrol configurations for the pins as used on that board. (This was tested using a GigaDevice GD25Q64E SPI NOR chip.) Signed-off-by: Alex Elder --- .../boot/dts/spacemit/k1-bananapi-f3.dts | 6 ++++++ arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi | 20 +++++++++++++++++++ arch/riscv/boot/dts/spacemit/k1.dtsi | 19 ++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts b/arch/riscv/b= oot/dts/spacemit/k1-bananapi-f3.dts index 6013be2585428..380d475d2f3f3 100644 --- a/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts +++ b/arch/riscv/boot/dts/spacemit/k1-bananapi-f3.dts @@ -44,6 +44,12 @@ &pdma { status =3D "okay"; }; =20 +&spi3 { + pinctrl-names =3D "default"; + pinctrl-0 =3D <&ssp3_0_cfg>; + status =3D "okay"; +}; + &uart0 { pinctrl-names =3D "default"; pinctrl-0 =3D <&uart0_2_cfg>; diff --git a/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi b/arch/riscv/boot= /dts/spacemit/k1-pinctrl.dtsi index 3810557374228..16c953eca2aaa 100644 --- a/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi +++ b/arch/riscv/boot/dts/spacemit/k1-pinctrl.dtsi @@ -28,4 +28,24 @@ pwm14-1-pins { drive-strength =3D <32>; }; }; + + ssp3_0_cfg: ssp3-0-cfg { + ssp3-0-no-pull-pins { + pinmux =3D , /* SCLK */ + , /* MOSI */ + ; /* MISO */ + + bias-disable; + drive-strength =3D <19>; + power-source =3D <3300>; + }; + + ssp3-0-frm-pins { + pinmux =3D ; /* FRM (frame) */ + + bias-pull-up =3D <0>; + drive-strength =3D <19>; + power-source =3D <3300>; + }; + }; }; diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spa= cemit/k1.dtsi index 66b33a9110ccd..a826cc1ac83d5 100644 --- a/arch/riscv/boot/dts/spacemit/k1.dtsi +++ b/arch/riscv/boot/dts/spacemit/k1.dtsi @@ -834,6 +834,25 @@ storage-bus { #size-cells =3D <2>; dma-ranges =3D <0x0 0x00000000 0x0 0x00000000 0x0 0x80000000>; =20 + spi3: spi@d401c000 { + compatible =3D "spacemit,k1-spi"; + reg =3D <0x0 0xd401c000 0x0 0x30>; + #address-cells =3D <1>; + #size-cells =3D <0>; + clocks =3D <&syscon_apbc CLK_SSP3>, + <&syscon_apbc CLK_SSP3_BUS>; + clock-names =3D "core", + "bus"; + resets =3D <&syscon_apbc RESET_SSP3>; + interrupts-extended =3D <&plic 55>; + spacemit,k1-ssp-id =3D <3>; + dmas =3D <&pdma 20>, + <&pdma 19>; + dma-names =3D "rx", + "tx"; + status =3D "disabled"; + }; + emmc: mmc@d4281000 { compatible =3D "spacemit,k1-sdhci"; reg =3D <0x0 0xd4281000 0x0 0x200>; --=20 2.48.1