From nobody Sun Sep 27 05:47:53 2026 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (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 ACA5530E821 for ; Fri, 4 Sep 2026 13:09:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527367; cv=none; b=UaFimDoz9PDs3O6tjPmzIBOrt02vS5T5ZKETZSjRI39ILY1qPA+FGYedPtMccM/Z7OCW1FdSm7h7ydUbU8ZlPIL0YfYKb0vTTmey2pyNPeHMgZkLo6XcaHbL6eh4qqebwiY15MaOF66wMJSm4lyf3t0DZ9LdZ1xm7pMUvGRRdOk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527367; c=relaxed/simple; bh=DXP7b9aeuGMlpb6EbUoiOrTeOWhkU0hKUHsr29rOjmk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HZ9R9JAnD2Pz/swit+YZUn33O9KhH5JA444GKZzBkHJtJg+e0lsf/AOuRPcd5flygTHOrUqAtIeDqd5BsfLTFdHOOljytu0i0MeAteubWTVCyNa6MRKRKXpeFlxQKL/cL0igGeNDbNbBIu0vzKT4QMk9LSWh7ggLssnvYQHA3ZI= 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=hqXlPFuS; arc=none smtp.client-ip=209.85.128.50 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="hqXlPFuS" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-4994d41ceb9so766175e9.2 for ; Fri, 04 Sep 2026 06:09:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788527364; x=1789132164; 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=t7o7pGTYJ1j9htjyKompyXPu5oGlEszk3L/eQl+FLhg=; b=hqXlPFuS6+NM7baHIqPdcb0pkKcP+P2FnxBeR0g0W6w+WA/iLZLKzDj+azJxR9j8Gs /W/eAjmGoAqL8loW5TAdoaEX2r0cOUHJhqSTAd6Adc1bQY9ymlFUOSg6uQGBJ7UDkWYp Y9B3R82NM9mG+M9OIuYUEmOjrZKcbf5TyvNEcCBm844eiOor0zw7r3SFfXIdSudANmrW j5h0LOrL/YHtWXhUom/MxRPAavMv2DENc295FOXn1aU06C6vkSIY08/jOPDBUZI/iS+p PrhXuoumdmp5aZw2ZC9WF/4wCDXXAO/UPtO7VW9/7rWFxuEa96jkGlr8EzXfqupet/kM YPvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788527364; x=1789132164; 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=t7o7pGTYJ1j9htjyKompyXPu5oGlEszk3L/eQl+FLhg=; b=QVLYL3aeSpq56wOJZmz9P1+giwcPLeWEDGI7cUGoCAhUV7HW5PFg+y1Ig1v483OCGB V38I/JZ1DfZwxMgQxc0/39mcNWvyAsJAA/HWk4KhfQUap5Pq2z0eFTbEAVeHz6OF12DR bRviVJMrhmzVn+tPXlgN4yVEl5yeOt/mxvaAm6lUJ0BsglCUMRmiX+i7wgCSskpCDHQO zYhTAlIJyueMUJI/LZOz0Lpz1AYI3AfNfCBDZrtGODa6X5bJVEab+/Oj/HyL7bzOz9+x 2IYuFA83nGoAyeqNC6GBsoEn+g05nrdnKIAJadONR7RWEWEI/x2m87AY4KcA9NDa0NrG VNug== X-Forwarded-Encrypted: i=1; AKwUvBxpRxo4l/XoDkUBU/71kk8XbDH4mIjMW+SXmrW/MDUbXEdJb/SkdRHOGTk+u0sLlw+xfKij+FVDzs5ExnM=@vger.kernel.org X-Gm-Message-State: AFuF++kMY0U8eDX9mQPTYHCLdBIKf+peSXg9/oCL9lJQHmB3aQYJ8iwC KB7HAsorCMto4q0ZSVeDGMvAWTRGHuAWwfDqJTis4/eQ7iRGoFbI9tLq X-Gm-Gg: AYBFou0qgxgjaqLVEsZN/qU2g+w1OCOUXArhNRjsYuOaj837dnDH0XhbLeVygC0JlJ3 5yCIXWULGEVu6ussPNfuj3K8G4+397gwBegOGqSI6y9ouO9RDSaJ7Wu4XtfpxR7RCSg6bjBRtDb e+B59IAISicP8PPu18A05V3uP7Q953uOx9llDvIG0kHCqza3VPCOX9XQ0ni8Zs5tkjS5Vz8VbA9 7I3u+jelSAiedSh224b04rPf0SKnEtYY3R0wDrmGh52Wn0v7Bb7abccfE9Ah7iAx+rG1ATTGIiD CTykAWI/pyJQpmF68/bx3oJHivXxygcPyk1FtlI/nxjpPfYP5dFe/brGjQI968Rz/TfwCV/2PZz b6BzjRNYPMOtjupB8xm8VqdWDliphuYZNqosfAr3Jr5TsQh5Rcc1jkDOwzHh98Ru3lckbBdWZDP F1Rpx+gjN2fQ+6IM+5YJyBENSu+LPwkRdoGmQkGCu6bArlmkEI8gmknhgjIBvoskPeYaOu0WfwZ 1zbSboOOYuHuwpdlzkKO17iAAOdgOlxZMp3ilhEZBtTK2zHPCKgDYvA5fr6GuJCxFxJP9rQDP6U 91s= X-Received: by 2002:a05:600c:8b6d:b0:49c:e363:c66e with SMTP id 5b1f17b1804b1-49cf82420afmr41030795e9.1.1788527363631; Fri, 04 Sep 2026 06:09:23 -0700 (PDT) Received: from OrangePi5-Plus.BB-HOME (20014C4E1B871500CB6EF488A18F9C42.dsl.pool.telekom.hu. [2001:4c4e:1b87:1500:cb6e:f488:a18f:9c42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49ce554d52esm135575435e9.3.2026.09.04.06.09.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 06:09:23 -0700 (PDT) From: Igor Paunovic To: Tomeu Vizoso , Oded Gabbay , Heiko Stuebner Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sidong Yang , Diederik de Haas , Sebastian Reichel , Jiaxing Hu , Nicolas Dufresne , Jonas Karlman , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Igor Paunovic Subject: [PATCH 1/7] accel/rocket: request the core clocks by name Date: Fri, 4 Sep 2026 15:08:52 +0200 Message-ID: <20260904130858.27803-2-royalnet026@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260904130858.27803-1-royalnet026@gmail.com> References: <20260904130858.27803-1-royalnet026@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" rocket_core_init() hands core->clks to devm_clk_bulk_get() without ever setting the .id members. The rocket_core array is allocated with devm_kcalloc() in rocket_device_init(), and rocket_probe() only fills in .rdev, .dev and .index, so all four clk_bulk_data entries are requested with a NULL con_id (unlike core->resets, whose ids are set a few lines above). clk_get(dev, NULL) ends up in of_clk_get_hw(np, 0, NULL), and of_parse_clkspec() only consults "clock-names" when a name was passed, so the index stays 0 for all four entries. Every entry therefore ends up holding a handle to the *first* clock of the DT "clocks" property, i.e. ACLK_NPUn. Nothing fails: probe succeeds and the driver believes it owns four different clocks. The consequence is that rocket_device_runtime_resume() prepares and enables the AXI clock four times, while hclk, pclk and - most importantly - the NPU compute clock ("npu", SCMI_CLK_NPU on RK3588) are never prepared or enabled by this driver at all. The NPU still works only because the Rockchip power-domain driver sets GENPD_FLAG_PM_CLK and its attach_dev() callback walks the device node with of_clk_get() and adds every clock to the pm_clk list, so genpd happens to keep the remaining clocks running. The bug is therefore latent today, but it means the driver holds no reference to the clock that actually feeds the NPU, which stands in the way of any future frequency scaling (OPP/devfreq) work. Found on an Orange Pi 5 Plus (RK3588) by reading the live clock tree: /sys/kernel/debug/clk/clk_summary shows four "fdab0000.npu" consumer handles on aclk_npu0 (and likewise on aclk_npu1/aclk_npu2 for the other two cores), while hclk_npu0, pclk_npu_root and scmi_clk_npu have no "fdab0000.npu" consumer at all - their only consumers are the "npu@fdab0000" handles created by the power-domain driver via of_clk_get(). Set the ids explicitly, in the order mandated by the binding (Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml): aclk, hclk, npu, pclk. After the change the driver holds one handle per distinct clock and clk_bulk_prepare_enable() covers all four. Note that this is a user-visible tightening for out-of-tree DTs: the old NULL-id requests resolved by index and succeeded no matter what "clock-names" contained, while the named requests fail probe with -ENOENT when one of the four names is missing. That is the right outcome for in-tree users - the binding requires exactly these four clock-names and rk3588-base.dtsi carries them on all three cores - but a DT that relied on the permissive lookup goes from silently running on the wrong clock handles to not probing at all, so record the change here where git log will find it. Fixes: ed98261b4168 ("accel/rocket: Add a new driver for Rockchip's NPU") Signed-off-by: Igor Paunovic Tested-by: Sidong Yang Tested-by: Diederik de Haas # NanoPC-T6 LTS, Nan= oPC-T6 Plus Reviewed-by: Sebastian Reichel Reviewed-by: Jiaxing Hu Signed-off-by: Jiaxing Hu --- drivers/accel/rocket/rocket_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rock= et_core.c index b3b2fa9ba645a..5dd260bacbff6 100644 --- a/drivers/accel/rocket/rocket_core.c +++ b/drivers/accel/rocket/rocket_core.c @@ -28,6 +28,10 @@ int rocket_core_init(struct rocket_core *core) if (err) return dev_err_probe(dev, err, "failed to get resets for core %d\n", cor= e->index); =20 + core->clks[0].id =3D "aclk"; + core->clks[1].id =3D "hclk"; + core->clks[2].id =3D "npu"; + core->clks[3].id =3D "pclk"; err =3D devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks); if (err) return dev_err_probe(dev, err, "failed to get clocks for core %d\n", cor= e->index); --=20 2.43.0 From nobody Sun Sep 27 05:47:53 2026 Received: from mail-wm2-f12.google.com (mail-wm2-f12.google.com [74.125.225.140]) (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 2545B31AABF for ; Fri, 4 Sep 2026 13:09:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527370; cv=none; b=XO+CWAUo8ftrhrEfsT3Gm+aqEWl6B8xjPXS5/P3FTQ92bwJyUMqGCMKjCMz65WzS6wULWxXmiS0AljoHBXWJWBxSFRPUARqeBmxDHrLYGW9Yys8riDrQ2uevqbH0V+9Phfd5fxWHyS8iRLHq+dp6rfmbcWiIFSJrJGbNuPKJCdw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527370; c=relaxed/simple; bh=xW3BlnlVGWV5PkWwqN6FwojNoL/psHoWMS2ENDfENlM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZwH947uICxz5gqDmDVew6k3y0c6ed7v6/oIfefkSt4frg7vR2UvS1UrqGllfVmB0I6e5JWLGAV3lxjnOXgACW6gDGc3vbJe+3EA541zG78zDbm/veFoNgr099ur+QCfejpQbKCwxyJ9t9WrawG2Ci7wSPdAIzUr3wL7NX1UhUg8= 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=bewhmzVs; arc=none smtp.client-ip=74.125.225.140 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="bewhmzVs" Received: by mail-wm2-f12.google.com with SMTP id 5b1f17b1804b1-49b4ba7fe26so267105e9.2 for ; Fri, 04 Sep 2026 06:09:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788527366; x=1789132166; 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=WkYtc8Zy+X1nQtQQfmPXyE49NKh145BAfNH3nX3cWHM=; b=bewhmzVshE+vs5j7IvSVKFK19ZN/1IuctXlRo2B/+dk2+lg8IKmenp3ym0ngoYeSyY nZCOmU27GuxUXBa/fJsy8KFfPQ2XdqUdjzEnxKV6Lrwq8NlgkPQGjr8TPIQONU+MK3wm WONkQ8QYQLFccwCghMX1kBrlFK3n0jY4DOMu6FR3xj0lW2cuJDO5Y+gsKhn4BaxH79NI 7CG2WKH5Y08g9ga+GbA1QpVYZ9A2zHrfWa7Iiz7COf6i7is9OPP9fVuv989Tlb9iRyRu +R0SXQwHhzsKSwjMKrJnMqFXQrxV8uTzzZz//PK0LHs5ke0v7o8LfeC4z0H32JwwCtXM 9USQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788527366; x=1789132166; 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=WkYtc8Zy+X1nQtQQfmPXyE49NKh145BAfNH3nX3cWHM=; b=hnUJpsQvHhX9t88SfMXSH1TEr43xWAZMwKWsvVRVwucvlhdVp3xytNBeep0ufDeema 7bXFWHo9waOuhLl67Pq8i4L2Qg/wg95GOgt+MUUuwLVkkgRB5VEEe4KPeytQWlDN+liE aT+GhHM+ZBZIbKC94d32nzXB8EElcAnQ18IvRyck3AEXgN1Qnw2fzs/NfsLkEbLzgW+j 2S5pynMEB5rdt/yjU+LTbKKv3dCma8FGe1oU4i39kz7c36Ly4VeTJRBmiAQnqies8zoq HDp0B9UGaI30lgky7MxAVI74pTKPsiUAxcmfUxdQoePPmhcIxOjCHT0091/QBEVk42l9 Qf2A== X-Forwarded-Encrypted: i=1; AKwUvByxSxHC5Py+cdapXOpMziwCdUxzDMMJUxNW+HozCdhbr/Vs+k1WpUFYKoVOvpH2AIEijIN6iOhlVdpSiQo=@vger.kernel.org X-Gm-Message-State: AFuF++m0TePSRaENZD6SO342zKsjbEM8mRNSs2vk3VoWFyqEYvT3twUv PQci0tZsKruoDkPdjUv1lZXzOkrciLFTU/yFpKjiWcjeyBa5L1pRhOV7 X-Gm-Gg: AYBFou2GYgUuMdxWbbOZ0lWLOzE2qQmkER1mG/CNugpXjYJ9JdKXxHZePOAz4B77PGx rQo6qyCqnITL8IciT1gML6/XIPLEJPvIPpJGkkXxMwIDPv8m1g+eBNmbzqWA4GlmpR3sWMDkdkF A7YCJI1nI38upQ9FvEIVdKH8As8L2gtLL7T2WmLx1nAHrgl8JDax4FhWfQfKRc2QeQyM41Qw2Du xBxoQJDmBPiqdG/ULFzYEDc85ikO+p5OPYtAEq5ERwQzYxdyJP2NCkk9kFG8Xw6pFfFDqXFw79m t4iOZIGj2t+WsbPWdR2cWW1cIsAwXIVRz8wi7mxhMKOP4RqUkVNZFnPGeQNCPVbFqBZugU10Nzi j9u4dXMr1YtRgqSuMWkSEBjM8h97kBHZplYKSWUr8w8ds6vLyyYeDYN47AToOPL/dikU6zjZZ2n CPAlN1+tJEGHLyNjZZBdEfpXj5Zy6LowljvSzH2u8h5z+Vm2H4WKQ/02qA938ltSSDYespFelli 0+KzdzHMtUN0GYG5NXAWbBCyS1cXPAfQkVF+6iKNQwLmCnMUDElBPgNOd/Lpd5GJ1a0ZOnQHzRg 1S2srgU5IwlxT38= X-Received: by 2002:a05:600c:34c5:b0:499:cef6:104c with SMTP id 5b1f17b1804b1-49cf823c012mr41127815e9.1.1788527365941; Fri, 04 Sep 2026 06:09:25 -0700 (PDT) Received: from OrangePi5-Plus.BB-HOME (20014C4E1B871500CB6EF488A18F9C42.dsl.pool.telekom.hu. [2001:4c4e:1b87:1500:cb6e:f488:a18f:9c42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49ce554d52esm135575435e9.3.2026.09.04.06.09.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 06:09:25 -0700 (PDT) From: Igor Paunovic To: Tomeu Vizoso , Oded Gabbay , Heiko Stuebner Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sidong Yang , Diederik de Haas , Sebastian Reichel , Jiaxing Hu , Nicolas Dufresne , Jonas Karlman , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Igor Paunovic Subject: [PATCH 2/7] dt-bindings: npu: rockchip: allow DVFS and thermal properties Date: Fri, 4 Sep 2026 15:08:53 +0200 Message-ID: <20260904130858.27803-3-royalnet026@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260904130858.27803-1-royalnet026@gmail.com> References: <20260904130858.27803-1-royalnet026@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" The three NPU cores on the RK3588 are fed by a single clock and a single supply, and the firmware accepts a fixed set of rates for that clock. Describing those rates as an operating-points-v2 table is what lets a driver scale the NPU instead of leaving it at whatever rate the bootloader set, so allow the property on the core node. Throttling the NPU from a thermal zone needs the same node to be usable as a cooling device, so allow #cooling-cells too. Both properties belong on the core that carries the shared clock, not on all three: the cores have no clock of their own and cannot be scaled or throttled independently. Naming one representative node for a shared frequency domain is the established shape, as in "Cpufreq cooling device on CPU0" in Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml. The schema cannot enforce that placement, because all three cores share a compatible string and a node name pattern, so which core carries them stays a devicetree convention. That is the same situation as for CPU cooling, where cpus.yaml does not restrict #cooling-cells to cpu@0 either. The example gains #cooling-cells; the operating-points-v2 property is exercised by the RK3588 devicetree later in this series. Signed-off-by: Igor Paunovic Assisted-by: LLM checkpatch dt_binding_check Acked-by: Conor Dooley --- .../bindings/npu/rockchip,rk3588-rknn-core.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-cor= e.yaml b/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.ya= ml index caca2a4903cd1..595aacfbf8608 100644 --- a/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml +++ b/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml @@ -42,6 +42,13 @@ properties: - const: npu - const: pclk =20 + "#cooling-cells": + description: + Present on the core that drives the shared NPU clock, which is the o= ne + the operating-points-v2 table below is attached to. The other cores + cannot be throttled independently of it. + const: 2 + interrupts: maxItems: 1 =20 @@ -50,6 +57,8 @@ properties: =20 npu-supply: true =20 + operating-points-v2: true + power-domains: maxItems: 1 =20 @@ -100,6 +109,7 @@ examples: clocks =3D <&cru ACLK_NPU0>, <&cru HCLK_NPU0>, <&scmi_clk SCMI_CLK_NPU>, <&cru PCLK_NPU_ROOT>; clock-names =3D "aclk", "hclk", "npu", "pclk"; + #cooling-cells =3D <2>; interrupts =3D ; iommus =3D <&rknn_mmu_0>; npu-supply =3D <&vdd_npu_s0>; --=20 2.43.0 From nobody Sun Sep 27 05:47:53 2026 Received: from mail-wm1-f48.google.com (mail-wm1-f48.google.com [209.85.128.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 7A78C331A53 for ; Fri, 4 Sep 2026 13:09:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527371; cv=none; b=KQU9bmHOy4RyDZtdtZsd5TH5EU6u23oMCGBJjQ7yxGJO1hFBm6H3PSuD8YK6KRJGFG5ZBvEyzCZ5xwl8++agH2CGzNEFrgOp1VwdqFZquqQaVYhsHEwWdgQ5BW9cCUGaf1AoEB0MLbH/rrl1V2ufksZUiB7zdeRx9I1M7Nuo+VQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527371; c=relaxed/simple; bh=DxBGNoaEFtQlXpGgig8NFJaF8scL78xQ+bsiJIm6WLE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pHKhJ30FHTOLMMJ+TnokeVZcgd30OLYkp4jrZESN1uIS+8wkPVa151zJzzyrpA/EIn/EfAYKj/cPX063csY3UgTyE21inob4+JypXWivCA+hQKQNLEeyHZ1AY2tk60b1q8XJ3Gh1Awbld56UQeo/CFpLWJL0EZM97TozYYxOuG8= 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=olH2mYH9; arc=none smtp.client-ip=209.85.128.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="olH2mYH9" Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-49b96433ca3so328315e9.3 for ; Fri, 04 Sep 2026 06:09:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788527368; x=1789132168; 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=kbG5tkNoyC999CbeFUHbHjzpSs9HHc0dLmddOFM6uZc=; b=olH2mYH9E46t6Om306g5So4mMj67KDlc8Z5FHwuja+1q4P29+4NcpaivlvMtSKBlkA 7uNjqH4wVEh9Q2JC9Zq9sPnpLXC/zZRidrppUSO5QRfHifSV0X0do3cC/jd9wYM+VN6k qT8pTWqM4XInTS/POpxnq0uNGFXT7esYNoAkIHjJkODQn6t/jVockQ07SGZCCn8sCnc2 xNWMNZubuaDZgdakxFNaoG1OzToOBl1aIx97KA+NZTzqnnjheW8Xqqe0EkQvKBHss0O9 lZGJyPAnl1jArBuHiod0Yo6u50AqoQvP67LfCcmXuzdfT2KtLdrjx9FycSKc3mvuSmv7 IyvQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788527368; x=1789132168; 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=kbG5tkNoyC999CbeFUHbHjzpSs9HHc0dLmddOFM6uZc=; b=lfObehllhywq7rmjyJbXIqw+uRidNRxmiYMPQ5EQcdCQ4+OBYEthjlGPFOlqxk6O04 WU3R+Xj2GNlexpDrwOsyQa4DLZK7wP1PUxLxcknBR+64BMitscOCg+1RQpie+06P2jEZ qFEEbmvoqi/aC2DfBgAzmjZmqdAbb7OhpSOVytUAXNSUwvZJYOHaLnVMa7/rAuOqvCj5 cXfsCsqXFnS75F2EghIEjeAFFg5+Swtb7mhfu1jW9TRUpHaBrgw6FH5uXNhST1O6RhSK fJC8oLoKeJIPSFOU82o7pi0QWwJgicUt/9IPhQEa3q2tFzHDNoROH+E5I19VrLBAVUG7 Xbcg== X-Forwarded-Encrypted: i=1; AKwUvBxoF8MBKQhlBIvevodDpi+MjyZvYRrfXkfuqK1M1fHMtYt32fmrNyXfdyZsoyHjiO7BrF4SA/X6G46vNg8=@vger.kernel.org X-Gm-Message-State: AFuF++mFZT8xQeabxCT8JT76gQJ9NFdzA9dysdI590ZCUWUeZTc/8+0d 7sul70H+FDBBOyyxxqx0k+kuGy4Pnj0BGYqoI01C1/LrFvqGOx/8bFNf X-Gm-Gg: AYBFou1WwT3rEshiZJqXTFFh+ySsx3LugJG8dDAmc+ln4C+Gx48O8Cro/UZJm7pfxSL SQ7E5cK2vehhjT5ToNur1HbgyOlBohXpp4yNy1tNQMTUp6HnKaxv4cZSDxTn44c6ReOMGx9tmN5 lL6ODQ6zgxyk+Lu2U4h0tMuhSV5IRxXxDLy0X6H83bgJivbt1LHvTSKf1J1nRX3bw+6ivqQz0Jr JyfG8BQyVbNppDzPmsFXV/uh+zYm98ns5U5Wo7LkocHNYSkbB/8TK8QNFZjLdRf95bnWSidDwYy KbdYyZrNKiialbCAZ4Gy13DUTCL0jKcbzkimX3b3aVTzQCHSFjciyvrSU4mwwRFOknvZEYpylEm zWo/X88vVK8erxzQ7bhKOZTVD9qXekcR7hsyQsfSdcVK6vMob9ZJqP1Muq1G6NIsVq1K1LB2Ehh D0oZqm0V2QRQJyA8gOHjt4h8iD8FMaMKTFoMjIBdhU8nST4sFLIfa81VXcNf+YbRSsX6EnGYuKA IQvr0zFjBxYtzW2ElgmbVlkFDglr3eMtBiwLtgjgs4ogdM1cSHUIhMy75FIW4rHEZ7Z X-Received: by 2002:a05:600c:a00d:b0:499:d95a:41f with SMTP id 5b1f17b1804b1-49cf7f51dcbmr42170965e9.0.1788527367548; Fri, 04 Sep 2026 06:09:27 -0700 (PDT) Received: from OrangePi5-Plus.BB-HOME (20014C4E1B871500CB6EF488A18F9C42.dsl.pool.telekom.hu. [2001:4c4e:1b87:1500:cb6e:f488:a18f:9c42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49ce554d52esm135575435e9.3.2026.09.04.06.09.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 06:09:27 -0700 (PDT) From: Igor Paunovic To: Tomeu Vizoso , Oded Gabbay , Heiko Stuebner Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sidong Yang , Diederik de Haas , Sebastian Reichel , Jiaxing Hu , Nicolas Dufresne , Jonas Karlman , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Igor Paunovic Subject: [PATCH 3/7] arm64: dts: rockchip: rk3588: add an OPP table for the NPU Date: Fri, 4 Sep 2026 15:08:54 +0200 Message-ID: <20260904130858.27803-4-royalnet026@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260904130858.27803-1-royalnet026@gmail.com> References: <20260904130858.27803-1-royalnet026@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" The NPU compute clock is driven by the firmware, which only accepts one of the rates in its own PVTPLL table: 300, 400, 500, 600, 700, 800, 900 and 1000 MHz through the PVTPLL, plus 200 MHz off GPLL. Anything else comes back as SCMI_INVALID_PARAMETERS, so the table has to name those rates exactly rather than describe a range. 200 MHz is included even though the vendor table stops at 300, because mainline pins the cores there with assigned-clock-rates and that is the rate the NPU boots and idles at. Leaving it out would put the boot state outside the table and give a driver nowhere to return to. Its voltage is the same 700 mV the vendor uses for 300 MHz, so it is conservative. The voltages are the vendor's, and the upper half of the table matches the GPU table in this file step for step: 700 MHz at 700 mV, 800 at 750, 900 at 800, 1000 at 850. There is no PVTM or binning here, for the same reason the GPU table has none: mainline uses conservative worst-case voltages instead of per-chip nvmem data. The table is attached to rknn_core_0 alone. All three cores share one clock and one supply and cannot be scaled independently, and the driver hangs its devfreq device off the core that carries the table. The full SoC range is described rather than a per-board subset, so that a board which cannot cool the upper rates drops them in its own .dts with a /delete-node/ on the OPP it does not want. A board may only delete OPPs that way, never invent intermediate ones: a rate that is not in the firmware's table is rejected outright. There is deliberately no opp-suspend property. The driver has to resume every core before it may touch the shared clock, so letting the devfreq core drive a suspend OPP from inside a runtime-suspend callback would deadlock against the driver's own governor worker. The driver records the boot rate and restores it itself instead. The consumer is the devfreq support added later in this series; until then the table is inert and the NPU keeps the fixed rate that assigned-clock-rates gives it today. rk3588j.dtsi does not include this file; it carries its own derated tables for the CPU clusters and the GPU, and it gets no NPU table here. That is deliberate. The J part is rated lower than the rates in this table and none of it can be measured on the hardware this was written on, so inventing a derated NPU table would be guessing. Its NPU node stays disabled, so nothing binds and the cooling map added later in this series is simply never resolved. The same rates and voltages were arrived at independently by Nicolas Dufresne in a proof of concept that was never posted to the list; his version differs in that it marks 200 MHz as opp-suspend, shares one table across all three cores and drops the assigned-clock-rates pins. Link: https://gitlab.collabora.com/nicolas/linux/-/commits/rock5b-npu-poc-4 Signed-off-by: Igor Paunovic Assisted-by: LLM checkpatch dtbs_check --- arch/arm64/boot/dts/rockchip/rk3588-opp.dtsi | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-opp.dtsi b/arch/arm64/boot= /dts/rockchip/rk3588-opp.dtsi index b5d630d2c879f..3711727020ed1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-opp.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-opp.dtsi @@ -151,6 +151,47 @@ opp-1000000000 { opp-microvolt =3D <850000 850000 850000>; }; }; + + npu_opp_table: opp-table-npu { + compatible =3D "operating-points-v2"; + + opp-200000000 { + opp-hz =3D /bits/ 64 <200000000>; + opp-microvolt =3D <700000 700000 850000>; + }; + opp-300000000 { + opp-hz =3D /bits/ 64 <300000000>; + opp-microvolt =3D <700000 700000 850000>; + }; + opp-400000000 { + opp-hz =3D /bits/ 64 <400000000>; + opp-microvolt =3D <700000 700000 850000>; + }; + opp-500000000 { + opp-hz =3D /bits/ 64 <500000000>; + opp-microvolt =3D <700000 700000 850000>; + }; + opp-600000000 { + opp-hz =3D /bits/ 64 <600000000>; + opp-microvolt =3D <700000 700000 850000>; + }; + opp-700000000 { + opp-hz =3D /bits/ 64 <700000000>; + opp-microvolt =3D <700000 700000 850000>; + }; + opp-800000000 { + opp-hz =3D /bits/ 64 <800000000>; + opp-microvolt =3D <750000 750000 850000>; + }; + opp-900000000 { + opp-hz =3D /bits/ 64 <900000000>; + opp-microvolt =3D <800000 800000 850000>; + }; + opp-1000000000 { + opp-hz =3D /bits/ 64 <1000000000>; + opp-microvolt =3D <850000 850000 850000>; + }; + }; }; =20 &cpu_b0 { @@ -188,3 +229,7 @@ &cpu_l3 { &gpu { operating-points-v2 =3D <&gpu_opp_table>; }; + +&rknn_core_0 { + operating-points-v2 =3D <&npu_opp_table>; +}; --=20 2.43.0 From nobody Sun Sep 27 05:47:53 2026 Received: from mail-wr1-f54.google.com (mail-wr1-f54.google.com [209.85.221.54]) (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 5A8D132B112 for ; Fri, 4 Sep 2026 13:09:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527374; cv=none; b=fmZcUVsSAVb/3ZlOOnscll7cWHR0YlzCNHSog41qxKZUWsx0LkP1P3dlctsb2E+gok991u4aAeWey/SZxYAGQXYzuRVFzW0x9/aeY4iKapibLRzO/wOLNrXXjW3SGkRS8pqiiUKm0e48sqMNTFxI6YENCQsYhbSWAWO8k757ojY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527374; c=relaxed/simple; bh=n0grn4S6vKiAtGhBq6W727/5/lW0S4rqzuGh21UW2MY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lghFkMMZfpNx8wqC+YZ3usDJNdSAdhZXtdABd0AW1514bbylrOnM6PI4gemzLztJ7xRJ6r6RQ9i38KMLNJY0m/lSC2deMQw2ctuFlCFrMECWNBpcFvqNetAWjeVmCEUqg/Bkmk8YUvzTQT3+sbhx9qY0VMbs5ngbX8hhRzshIuc= 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=L/of0B1h; arc=none smtp.client-ip=209.85.221.54 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="L/of0B1h" Received: by mail-wr1-f54.google.com with SMTP id ffacd0b85a97d-482e7093e75so131686f8f.1 for ; Fri, 04 Sep 2026 06:09:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788527370; x=1789132170; 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=0Nc493Zxn3lB2uIUk0uEHaMMSyj53T93ezhvvuGgReI=; b=L/of0B1hbxlldHquHgfbS1bCXKJRdF4ZE9AZYGhFPZXJbW5SzMqTpMsDoor4k7oZXY /3j7BmRFcPaA8bgIG4nQhpFsN7J0hfA++Bum9LTXB8FQ1rIL7Jhc+lXC1EtU9H1zdgT8 n5gqpYt3RWrJt3ogD7p/6LEb25B/3ZSvEcHOc/zDKnKWngdWoK/YhsLTCVLdQx8ne66E d6Zfcv+MU3lgmTV8/S+Z0tpy6buRUtMT9BO4C8hGYFJK01/cErfO9ol6cFGPKpC4JYe1 KENrgrXaFXa+J6kA/uGkgP1D4FtECHagMSDdbSDOULfmP51SJGkhjhHE0C302I+8HRX/ kzsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788527370; x=1789132170; 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=0Nc493Zxn3lB2uIUk0uEHaMMSyj53T93ezhvvuGgReI=; b=VfYC+aWxO7XX/6xXuieJ3NBFGqF5WaYun7e4yvjufQlSDmmq9+z5A2+CS9rS4XCRu1 AtaC29GeUcGY9Ii+8fKG4xBU8zH+v/wSsm5EnxDHp++RyTDhLNvvkjpkyOXhkchZ6IZJ 1em7A+UEVKbtHu67FEF9uSgUFgxZYDE6XvMFP4q0V7K/u9sY7f5LNWvjYGHGA0urxia5 lO1xuzWm109JEdyCe7sgrVLsHWI944tCP7rbDDd5Ouzx+3l/2UcPdtYKPyREqvcvB4yA HD1YpTGgMsfmQskhQTEcL+Wb43jSvvR2kokWNhZTFeecbBj8YeStUg7RWq/3Aw9Dth3y Aqeg== X-Forwarded-Encrypted: i=1; AKwUvByhSzP3959xsCKBphi2Wbb2uhMV8g5Q1ZwPYKcJShbJ40Uynj9WTGHh+ss04FBxObJ/zeBxpYtcCADpLSY=@vger.kernel.org X-Gm-Message-State: AFuF++mjBrhoX4WqfTSq3zmPBY2h2kZnbEGaJHD8kGtu+CsxlNJmx00t WhroUzS5o2di/cGusq7X9BLC+lX5V6s1YkmQTg0HV+NxiEu/KQS22Ih2zwvcXA== X-Gm-Gg: AYBFou0jsKrfF1NNuE+ShkRqAQKQ0qAA5eJfvaMCpfzQ8qLZ8onteSSwsuuXG1jNhKa OMyq1CBq4a89r8iVhwWdz2IOBK/AXEJtDPFxTQEX+a+Q4VrJ/SKkeTetmm8duGHAeU7yIa37HMp JkhemWUNr23e8mo5LA4QL9IaJi3TAmHXv3A1ff4dDC+KBazDUrw8Q2Lgq1/l6ExYoOiNjJ0/lvH lPMRy3bn3KfUyKBSuBoB/H55dH/qboh70q8bfEAg3O85hIqUr+d2EI/WTlvFGv1tcA4Go3Z0LpP JpxWBRNjDQm+QtmWlXAp6uVJI1jQqf3J1h9e4i/r1dXhCDeMzvuMaTRFYReDQO8EfWU+nalDiPc Swed/Hd7/lC6OyEZYr+2TQ8GeddhYmjActqWDY5KoAU5jHamQI9nYHHWCl2uxnhv2Y+fbSypr/d d1qVJl+ux7RxL3KtIOmmyASNCeE9jJxBBbcpcebQWZSFQnBMAF2XbEoCSmogZCij0ohzKVWVD8h ad1olNAVOVGFEyGHgZS/pPOQnTTdkPRhS+CWr8VKJYp4JpqvOsZUHwvr9zIbhkILzZ9 X-Received: by 2002:a05:600c:1d26:b0:49c:cbf4:572b with SMTP id 5b1f17b1804b1-49cf8245fe1mr47842485e9.2.1788527370519; Fri, 04 Sep 2026 06:09:30 -0700 (PDT) Received: from OrangePi5-Plus.BB-HOME (20014C4E1B871500CB6EF488A18F9C42.dsl.pool.telekom.hu. [2001:4c4e:1b87:1500:cb6e:f488:a18f:9c42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49ce554d52esm135575435e9.3.2026.09.04.06.09.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 06:09:29 -0700 (PDT) From: Igor Paunovic To: Tomeu Vizoso , Oded Gabbay , Heiko Stuebner Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sidong Yang , Diederik de Haas , Sebastian Reichel , Jiaxing Hu , Nicolas Dufresne , Jonas Karlman , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Igor Paunovic Subject: [PATCH 4/7] accel/rocket: restore the NPU clock boot rate before powering the cores down Date: Fri, 4 Sep 2026 15:08:55 +0200 Message-ID: <20260904130858.27803-5-royalnet026@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260904130858.27803-1-royalnet026@gmail.com> References: <20260904130858.27803-1-royalnet026@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" The compute clock is generated by a PVTPLL that lives inside the NPU power island. Powering an island up while that clock is above the rate the bootloader left it at does not work: the domain never acks the power-on, and the first register access into it afterwards takes an asynchronous SError. So the rate has to be back down before the last core goes away. Nothing in the driver raises the clock today, which makes this a no-op on its own, but it is the guard that has to be in the tree before anything does, and the next patches do. The .shutdown hook is the same guard for the handover: once devfreq is driving the clock, a reboot or a kexec would otherwise pass the raised rate to the next kernel, which powers the islands up before it looks at it. What this cannot do is rescue a rate it did not set - the rate read at probe is taken as the boot rate whatever it is. The rate is read at probe rather than hardcoded. Mainline pins the RK3588 cores at 200 MHz with assigned-clock-rates, but that is a devicetree property, not a property of the hardware, and a SoC whose devicetree does not set it would be left running at a rate this driver had invented. All three cores share the clock, so only the last core to suspend may lower it; the others just drop the count. Lowering it is safe with the islands already down, because the firmware serves the boot rate from GPLL and writes only CRU clock selectors to get there, never a register inside the NPU. Signed-off-by: Igor Paunovic Assisted-by: LLM sparse checkpatch --- drivers/accel/rocket/rocket_core.c | 12 +++++++++ drivers/accel/rocket/rocket_device.h | 10 +++++++ drivers/accel/rocket/rocket_drv.c | 40 ++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rock= et_core.c index 5dd260bacbff6..61200e5d5ac0d 100644 --- a/drivers/accel/rocket/rocket_core.c +++ b/drivers/accel/rocket/rocket_core.c @@ -12,6 +12,7 @@ #include =20 #include "rocket_core.h" +#include "rocket_device.h" #include "rocket_job.h" =20 int rocket_core_init(struct rocket_core *core) @@ -36,6 +37,17 @@ int rocket_core_init(struct rocket_core *core) if (err) return dev_err_probe(dev, err, "failed to get clocks for core %d\n", cor= e->index); =20 + /* + * Record what the compute clock was running at before anything here + * touched it, on the first core to probe. Reading it rather than + * hardcoding a rate keeps this working on a SoC whose devicetree does + * not pin the clock with assigned-clock-rates. + */ + if (!core->rdev->npu_clk) { + core->rdev->npu_clk =3D core->clks[2].clk; + core->rdev->npu_boot_rate =3D clk_get_rate(core->rdev->npu_clk); + } + core->pc_iomem =3D devm_platform_ioremap_resource_byname(pdev, "pc"); if (IS_ERR(core->pc_iomem)) { dev_err(dev, "couldn't find PC registers %ld\n", PTR_ERR(core->pc_iomem)= ); diff --git a/drivers/accel/rocket/rocket_device.h b/drivers/accel/rocket/ro= cket_device.h index c62d567010696..466ebc4c26a8a 100644 --- a/drivers/accel/rocket/rocket_device.h +++ b/drivers/accel/rocket/rocket_device.h @@ -20,6 +20,16 @@ struct rocket_device { struct rocket_core *cores; unsigned int num_cores; unsigned int max_cores; + + /* + * The cores have no clock of their own: one clock feeds all of them, + * so any core's handle refers to the same thing. npu_boot_rate is the + * rate it was left at before the driver touched it, and active_cores + * counts the cores that are runtime resumed right now. + */ + struct clk *npu_clk; + unsigned long npu_boot_rate; + atomic_t active_cores; }; =20 struct rocket_device *rocket_device_init(struct platform_device *pdev, diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocke= t_drv.c index 2bcfe4ab3c68f..b7199de57ccc7 100644 --- a/drivers/accel/rocket/rocket_drv.c +++ b/drivers/accel/rocket/rocket_drv.c @@ -231,6 +231,28 @@ static int find_core_for_dev(struct device *dev) return -1; } =20 +/* + * Put the compute clock back where the bootloader had it. The cores share + * this clock, so this is only correct once none of them is running any mo= re. + * + * Lowering the rate is safe with the power islands down: the firmware ser= ves + * the boot rate from GPLL and touches only the CRU clock selectors on the= way + * there, none of the NPU's own registers. + */ +static void rocket_npu_restore_boot_rate(struct rocket_device *rdev) +{ + int err; + + if (!rdev->npu_clk) + return; + + err =3D clk_set_rate(rdev->npu_clk, rdev->npu_boot_rate); + if (err) + dev_warn(rdev->cores[0].dev, + "failed to restore the NPU boot rate of %lu Hz: %d\n", + rdev->npu_boot_rate, err); +} + static int rocket_device_runtime_resume(struct device *dev) { struct rocket_device *rdev =3D dev_get_drvdata(dev); @@ -246,6 +268,8 @@ static int rocket_device_runtime_resume(struct device *= dev) return err; } =20 + atomic_inc(&rdev->active_cores); + return 0; } =20 @@ -262,6 +286,9 @@ static int rocket_device_runtime_suspend(struct device = *dev) =20 clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks), rdev->core= s[core].clks); =20 + if (atomic_dec_and_test(&rdev->active_cores)) + rocket_npu_restore_boot_rate(rdev); + return 0; } =20 @@ -270,9 +297,22 @@ EXPORT_GPL_DEV_PM_OPS(rocket_pm_ops) =3D { SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) }; =20 +/* + * A kexec or a reboot hands the next kernel whatever rate is set here, and + * that kernel will power the islands up before it looks at the clock. + */ +static void rocket_shutdown(struct platform_device *pdev) +{ + struct rocket_device *rdev =3D dev_get_drvdata(&pdev->dev); + + if (rdev) + rocket_npu_restore_boot_rate(rdev); +} + static struct platform_driver rocket_driver =3D { .probe =3D rocket_probe, .remove =3D rocket_remove, + .shutdown =3D rocket_shutdown, .driver =3D { .name =3D "rocket", .pm =3D pm_ptr(&rocket_pm_ops), --=20 2.43.0 From nobody Sun Sep 27 05:47:53 2026 Received: from mail-wr1-f50.google.com (mail-wr1-f50.google.com [209.85.221.50]) (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 6043634D3B9 for ; Fri, 4 Sep 2026 13:09:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527377; cv=none; b=lc3FfCbTbZD4iNKXAUEK/bJCd8owgoYl8MDww12JfNcMjPbT6Rov+hRT6mV7H03kaK38vWoA+V+eZ3tb6l2P9BbAe+v+PoLJObkKWxegmV3Y/rMO1n+Sx/dW+Qdg0cWjxKn7vkc/0fjL/icSh25n/E53eKPO/xO9nvO1CBlnB6Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527377; c=relaxed/simple; bh=Kx70qX9n2KXoovdIVnf/VHa464YQMYC89O2vWW8KgV0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UiFtbWHXjdtn+qZ5Af0Irg7xsURd3gzDAIhw2javoe2Uwmm9P4exE9+3mwrA+z2x1cxtvEt+UcFNRByljQBLupJyNF7mj8XOdnuCc+X3lhf8V/fgWfFu5qFeEXD/7STVte9T5n863Xcx2H+pbfyqZJfiOnu/eEEX39CtcdMSL5k= 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=f11rTOyS; arc=none smtp.client-ip=209.85.221.50 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="f11rTOyS" Received: by mail-wr1-f50.google.com with SMTP id ffacd0b85a97d-4843751b694so153720f8f.2 for ; Fri, 04 Sep 2026 06:09:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788527372; x=1789132172; 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=YJGYKkSgbzFZQ6ZkgtL51SBo2xzg1ldls2lL4TqE+fM=; b=f11rTOySnxoFbM9cT6LMo7G3Y0OrIhBGZG5o+ezyMEX+/AvNNrvXbW6CyEqRjxIYpO IW+8VYg7zrzrLP0XeH3XLVFouyEAMMZ7KMqeFMO84hjTbU3UzO1+gGW9NULElI8Cd6aC xSuwqTAnB7zGm4U0uYg1EZvDlus/drgcdDPpnBaaCG2yRhVrkUvSIAMlUK244urBBgkU InPl8+57zJfRL3p4XXTxfflE/q+V62iUv0BU2w0xSrHaWm/jLPT3StCJXRoCbta1QEdA 2MvcRnsAkszUhIWPGaVu882Rjmd4tyP8nq2NUcCzZlnz9j4EFWaVkM9lwidGVcYrGlv5 DRxg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788527372; x=1789132172; 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=YJGYKkSgbzFZQ6ZkgtL51SBo2xzg1ldls2lL4TqE+fM=; b=swXb/oHWmY/Mx+JTUJ0CjwBsaYRw2Mmr2+8njHudcrkuAhM7dD8YkklbwF7PCZWlzL v0lxY2mQrNV85oZemomM2VJCa+/iJwTK+fOf4pV3P5+dVm7tHts9xGwIAD1XAeMkHS2s vSrpOa1bwGlPpTJCYCis60O2xh74OSsjFW4QZaF93/VoupVGmyGv1oitjM+q/86lpveB cGN/8Ng3D6nxt/pQW/cv68aSp0ma3k/LA0S/yjq1P7pJHqa1wXuj5mZ6WvUraGoNEoll tobIH8M+cW5GbjJwtyLviddjv51XmxRuadtC9pJ7018nHEmE0CYwZDBYMss4Qr8UCogB pSWw== X-Forwarded-Encrypted: i=1; AKwUvByAzexh1c2t+W9zlKn4BlnxmjBDWQajP/FUqFKqGt5o6hXa3pAW2ZMX9U+9uRXAaBihELt1eO3A32Vwy4M=@vger.kernel.org X-Gm-Message-State: AFuF++nQpV+fSuHJJRQ828GrWw7dz7tApd241Ljjas2KPEGYWPO2Zq6Y imtQMkaf7b2ClRnHsNhL6CrYpE4TXOfb2YB2uyIwtJqCI0PvKyB39CW4 X-Gm-Gg: AYBFou3GjhDLmwwL/Tc+ZW2KhOdGBrGtkqqdzXKXKulql7rLYOFMfSwIjAMLBsnr8f0 zDKwQH3peGcDcZ6APi7U6Hf6V3Kc6RO5r+4dYxSfJuk3NW3VnbiD63327w7nCBL3xa172X32z/h 6lIZmzWaDKTMZ7XuwCv93tC+VWEqGJzf46ZZu0anoewvphpwenJgJkl68+2wzBMjPiafG34eunj sH5T8TI0qeZ3jJgA0rwtzm0PAFoR8kamohX/fWel2EPUV+i4a92oCesqgPi5IEiSvcmyBQlsn5T zSnQReK/edpcT70D+40AOF2yrDXbPb5Hrox9v2mcgyatQGZTAKUa2VJZGjUC+ViDNrHPlGgCNtY q3QJB/2PEV1Dy/ZUOCiRPpuHfXucsVHBVnZe0opGDhVBBc99dd/PjNSekUj8UYQ+IpAJIKOlnWh ub5+FxBJenSBpS6Z8AAVLV/oV0M6GI1ezHIhSF2tQzAJX0rXoW7gG09qX7sgDfk7dr4RNaWhx5b kHWgr/1+6ZinQnBFfkrk7P/wcxfiko5N/1u8MxI6NdjtqfL2m6KH41zh8oRkVRpyEw7AG9MM1LZ qYw= X-Received: by 2002:a05:600c:1d26:b0:49c:cbf4:572b with SMTP id 5b1f17b1804b1-49cf8245fe1mr47845035e9.2.1788527372234; Fri, 04 Sep 2026 06:09:32 -0700 (PDT) Received: from OrangePi5-Plus.BB-HOME (20014C4E1B871500CB6EF488A18F9C42.dsl.pool.telekom.hu. [2001:4c4e:1b87:1500:cb6e:f488:a18f:9c42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49ce554d52esm135575435e9.3.2026.09.04.06.09.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 06:09:31 -0700 (PDT) From: Igor Paunovic To: Tomeu Vizoso , Oded Gabbay , Heiko Stuebner Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sidong Yang , Diederik de Haas , Sebastian Reichel , Jiaxing Hu , Nicolas Dufresne , Jonas Karlman , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Igor Paunovic Subject: [PATCH 5/7] accel/rocket: add devfreq support Date: Fri, 4 Sep 2026 15:08:56 +0200 Message-ID: <20260904130858.27803-6-royalnet026@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260904130858.27803-1-royalnet026@gmail.com> References: <20260904130858.27803-1-royalnet026@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" The NPU has run at whatever rate the devicetree pinned it to since the driver was merged, which on the RK3588 is 200 MHz. The hardware reaches 1 GHz, and the firmware will change the rate on request, so let devfreq drive it from how busy the cores actually are. One devfreq device drives all of the cores, because they have one clock and one supply between them and cannot be scaled apart. It hangs off the core that carries the OPP table in the devicetree, found by looking for the property rather than by taking core 0: the core index is handed out in probe order, which is not the order the cores are written in. The awkward part is that the clock is generated by a PVTPLL that sits inside the NPU power islands. An island powered up while the clock is above the rate the bootloader left never acknowledges the power-on, and the first register access into it afterwards takes an asynchronous SError. So before the rate goes up, every core is runtime resumed, and the references are held for as long as the clock stays raised. While they are held no core can suspend, so no island can transition at all. That is what makes a raised clock safe rather than merely unlikely to be caught out, and it is why the guard and the thing it guards arrive in the same commit: no commit in the tree ever raises the rate without it. The cost is that a boosted NPU does not power-gate individual cores. It is paid only above the boot rate; at the boot rate the references are dropped and runtime PM behaves as before. What that costs in milliwatts has not been measured on this board, and I would rather say so than guess. A measurement, or a design that does not need the references at all, would both be welcome. Utilisation is aggregated as the maximum over the cores, not the sum. The rate has to satisfy the busiest core, and summing would report one saturated core out of three as a third of the load and clock down underneath it. Whether that is the right aggregation for a shared clock is a fair question for review. The driver does not call devfreq_suspend_device() and devfreq_resume_device() itself, which is a deviation from panfrost, panthor, lima and msm. It ends in cancel_delayed_work_sync() on the governor's own worker, and that worker is what calls back into ->target(), which resumes every core; from a runtime-suspend callback that waits for a worker that is waiting for the same callback to finish. An active-core count narrows the window but does not close it. In its place, ->target() returns early when the rate is unchanged, so a governor tick on an idle NPU costs one comparison and resumes nothing. System suspend is a different matter, and there the call does happen: dpm_suspend() runs devfreq_suspend() over every registered devfreq before it walks the devices, from a path that holds none of this driver's locks. What the driver adds is lowering the rate and dropping the references from every core's ->suspend, not only the one that owns the devfreq device. pm_runtime_force_suspend() powers a core down whatever the usage count says, and the owning core is suspended last, so a suspend that aborts partway would otherwise leave the clock raised over islands that are already gated. The clock and the supply are claimed in one dev_pm_opp_set_config() call before the table is added. Naming the clock there is not optional: with no name the OPP core takes the first clock in the node, which is the bus clock, and would scale that instead of the compute clock. Configuration and table are set up on the core that carries them in the devicetree, which is not the device being probed at the time, so none of it can be devres: the call hands back a token, and rocket_devfreq_fini() releases it, and the table, by hand. Leaving it to devres would make a later bind fail on an OPP table the previous teardown never emptied. The rate is changed through dev_pm_opp_set_rate(), so that the supply moves with it, which also means nothing may set the rate behind the OPP core's back: it caches the OPP it last applied and skips a repeat request for it, so a raw clk_set_rate() would turn the next request for a raised rate into a silent no-op with sysfs reporting a rate the hardware was not running. The boot-rate restore added in the previous patch is converted accordingly. ->get_cur_freq() and the status callback report the rate this driver last programmed rather than asking the clock. devfreq queries them from sysfs with no runtime PM reference of its own, and the answer has to be a rate from the OPP table or devfreq_get_freq_level() will not find it and every transition is logged as unknown. A devicetree with no OPP table is not an error: the driver returns without a devfreq device and the NPU keeps its boot rate, as before this patch. The governor thresholds are a starting point taken from the other accelerators in tree, not a measurement. Inference workloads have not been profiled against them. Signed-off-by: Igor Paunovic Assisted-by: LLM sparse checkpatch --- drivers/accel/rocket/Kconfig | 2 + drivers/accel/rocket/Makefile | 1 + drivers/accel/rocket/rocket_core.h | 11 + drivers/accel/rocket/rocket_devfreq.c | 458 ++++++++++++++++++++++++++ drivers/accel/rocket/rocket_devfreq.h | 55 ++++ drivers/accel/rocket/rocket_device.h | 3 + drivers/accel/rocket/rocket_drv.c | 80 ++++- drivers/accel/rocket/rocket_job.c | 7 + 8 files changed, 605 insertions(+), 12 deletions(-) create mode 100644 drivers/accel/rocket/rocket_devfreq.c create mode 100644 drivers/accel/rocket/rocket_devfreq.h diff --git a/drivers/accel/rocket/Kconfig b/drivers/accel/rocket/Kconfig index 16465abe06607..00ee845c871fa 100644 --- a/drivers/accel/rocket/Kconfig +++ b/drivers/accel/rocket/Kconfig @@ -8,6 +8,8 @@ config DRM_ACCEL_ROCKET depends on MMU select DRM_SCHED select DRM_GEM_SHMEM_HELPER + select PM_DEVFREQ + select DEVFREQ_GOV_SIMPLE_ONDEMAND help Choose this option if you have a Rockchip SoC that contains a compatible Neural Processing Unit (NPU), such as the RK3588. Called by diff --git a/drivers/accel/rocket/Makefile b/drivers/accel/rocket/Makefile index 3713dfe223d6e..e0944b3e68121 100644 --- a/drivers/accel/rocket/Makefile +++ b/drivers/accel/rocket/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_DRM_ACCEL_ROCKET) :=3D rocket.o rocket-y :=3D \ rocket_core.o \ rocket_device.o \ + rocket_devfreq.o \ rocket_drv.o \ rocket_gem.o \ rocket_job.o diff --git a/drivers/accel/rocket/rocket_core.h b/drivers/accel/rocket/rock= et_core.h index f6d7382854ca9..7ada497571bf9 100644 --- a/drivers/accel/rocket/rocket_core.h +++ b/drivers/accel/rocket/rocket_core.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include =20 @@ -55,6 +56,16 @@ struct rocket_core { struct drm_gpu_scheduler sched; u64 fence_context; u64 emit_seqno; + + /* + * Utilisation seen by devfreq, guarded by rdev->devfreq.busy_lock. A + * core runs one task at a time, so a flag is exact here and, unlike a + * counter, cannot be left skewed by a job the reset path tore down. + */ + bool busy; + ktime_t busy_time; + ktime_t idle_time; + ktime_t time_last_update; }; =20 int rocket_core_init(struct rocket_core *core); diff --git a/drivers/accel/rocket/rocket_devfreq.c b/drivers/accel/rocket/r= ocket_devfreq.c new file mode 100644 index 0000000000000..9d923a0b6ea30 --- /dev/null +++ b/drivers/accel/rocket/rocket_devfreq.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2025 Igor Paunovic */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rocket_core.h" +#include "rocket_device.h" +#include "rocket_devfreq.h" + +/* + * One clock and one supply feed all of the NPU cores, so a single devfreq + * device drives them together. It hangs off the core that carries the OPP + * table in the devicetree. + * + * The awkward part is that the clock is generated by a PVTPLL that sits + * inside the NPU power islands. An island that is powered up while the cl= ock + * is above the rate the bootloader left never acknowledges the power-on, = and + * the first register access into it afterwards takes an asynchronous SErr= or. + * + * Lowering the rate is fine at any time, because the firmware serves the = boot + * rate from GPLL and only writes CRU clock selectors on the way there. Ra= ising + * it is not, so before the rate goes up every core is runtime resumed and= the + * references are kept for as long as the clock stays raised. While they a= re + * held no core can suspend, so no island can transition at all, which is = the + * property that makes the raised clock safe rather than merely unlikely t= o be + * caught out. + * + * The cost is that a busy NPU does not power-gate individual cores. It is + * paid only above the boot rate: at the boot rate the references are drop= ped + * and runtime PM behaves exactly as it did before this file existed. The = cost + * in milliwatts has not been measured on this board, and a measurement or= a + * better idea would both be welcome. + */ + +static void rocket_devfreq_update_utilisation(struct rocket_core *core) +{ + ktime_t now =3D ktime_get(); + ktime_t elapsed =3D ktime_sub(now, core->time_last_update); + + if (core->busy) + core->busy_time =3D ktime_add(core->busy_time, elapsed); + else + core->idle_time =3D ktime_add(core->idle_time, elapsed); + + core->time_last_update =3D now; +} + +static int rocket_devfreq_hold_all(struct rocket_device *rdev) +{ + unsigned int i; + int ret; + + for (i =3D 0; i < rdev->num_cores; i++) { + ret =3D pm_runtime_resume_and_get(rdev->cores[i].dev); + if (ret < 0) { + while (i--) + pm_runtime_put_autosuspend(rdev->cores[i].dev); + + return ret; + } + } + + return 0; +} + +static void rocket_devfreq_release_all(struct rocket_device *rdev) +{ + unsigned int i; + + for (i =3D 0; i < rdev->num_cores; i++) + pm_runtime_put_autosuspend(rdev->cores[i].dev); +} + +/* Caller holds rdev->devfreq.lock. */ +static int rocket_devfreq_set_rate(struct rocket_device *rdev, unsigned lo= ng freq) +{ + struct rocket_devfreq *rdevfreq =3D &rdev->devfreq; + struct device *dev =3D rdevfreq->owner->dev; + int ret; + + ret =3D dev_pm_opp_set_rate(dev, freq); + if (ret) { + dev_err(dev, "failed to set the NPU rate to %lu Hz: %d\n", freq, ret); + return ret; + } + + WRITE_ONCE(rdevfreq->cur_freq, freq); + + return 0; +} + +static int rocket_devfreq_target(struct device *dev, unsigned long *freq, = u32 flags) +{ + struct rocket_device *rdev =3D dev_get_drvdata(dev); + struct rocket_devfreq *rdevfreq =3D &rdev->devfreq; + struct dev_pm_opp *opp; + int ret; + + opp =3D devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) + return PTR_ERR(opp); + dev_pm_opp_put(opp); + + guard(mutex)(&rdevfreq->lock); + + /* + * The governor calls this on every tick, including the ticks where it + * arrives at the rate the NPU is already running. Without this an idle + * NPU would resume all of its cores ten times a second to set the rate + * they already have. + */ + if (*freq =3D=3D READ_ONCE(rdevfreq->cur_freq)) + return 0; + + if (!rdevfreq->cores_held) { + ret =3D rocket_devfreq_hold_all(rdev); + if (ret) { + /* + * Runtime PM is disabled on the way into system + * suspend, so this is the ordinary way for a governor + * tick that raced with it to end. + */ + dev_dbg(dev, "cannot resume the NPU cores to change rate: %d\n", ret); + return ret; + } + rdevfreq->cores_held =3D true; + } + + ret =3D rocket_devfreq_set_rate(rdev, *freq); + + /* At the boot rate the cores are free to suspend again. */ + if (READ_ONCE(rdevfreq->cur_freq) <=3D rdev->npu_boot_rate) { + rdevfreq->cores_held =3D false; + rocket_devfreq_release_all(rdev); + } + + return ret; +} + +static int rocket_devfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *status) +{ + struct rocket_device *rdev =3D dev_get_drvdata(dev); + ktime_t busy =3D 0, total =3D 0; + unsigned int i; + + scoped_guard(spinlock_irqsave, &rdev->devfreq.busy_lock) { + for (i =3D 0; i < rdev->num_cores; i++) { + struct rocket_core *core =3D &rdev->cores[i]; + + rocket_devfreq_update_utilisation(core); + + /* + * The cores share the clock, so what the rate has to + * satisfy is the busiest of them. Adding the cores up + * instead would report one saturated core out of three + * as a third of the load, and clock down underneath it. + */ + busy =3D max(busy, core->busy_time); + total =3D max(total, ktime_add(core->busy_time, core->idle_time)); + + core->busy_time =3D 0; + core->idle_time =3D 0; + } + } + + status->busy_time =3D ktime_to_ns(busy); + status->total_time =3D ktime_to_ns(total); + status->current_frequency =3D READ_ONCE(rdev->devfreq.cur_freq); + + dev_dbg(dev, "busy %lu total %lu %lu%% freq %lu MHz\n", + status->busy_time, status->total_time, + status->busy_time * 100 / MAX(status->total_time, 1), + status->current_frequency / 1000 / 1000); + + return 0; +} + +/* + * Report what this driver last programmed, not what the firmware says. de= vfreq + * asks for the current frequency from sysfs as well, without a runtime PM + * reference of its own, and the answer has to be one of the rates in the = OPP + * table or devfreq_get_freq_level() will not find it and every transition= will + * be logged as unknown. + */ +static int rocket_devfreq_get_cur_freq(struct device *dev, unsigned long *= freq) +{ + struct rocket_device *rdev =3D dev_get_drvdata(dev); + + *freq =3D READ_ONCE(rdev->devfreq.cur_freq); + + return 0; +} + +static struct devfreq_dev_profile rocket_devfreq_profile =3D { + .timer =3D DEVFREQ_TIMER_DELAYED, + .polling_ms =3D 50, + .target =3D rocket_devfreq_target, + .get_dev_status =3D rocket_devfreq_get_dev_status, + .get_cur_freq =3D rocket_devfreq_get_cur_freq, +}; + +void rocket_devfreq_record_busy(struct rocket_core *core) +{ + struct rocket_devfreq *rdevfreq =3D &core->rdev->devfreq; + + if (!rdevfreq->devfreq) + return; + + scoped_guard(spinlock_irqsave, &rdevfreq->busy_lock) { + rocket_devfreq_update_utilisation(core); + core->busy =3D true; + } +} + +/* + * Idempotent on purpose: a job that times out is torn down by the reset p= ath, + * which cannot know whether the completion interrupt got there first. + */ +void rocket_devfreq_record_idle(struct rocket_core *core) +{ + struct rocket_devfreq *rdevfreq =3D &core->rdev->devfreq; + + if (!rdevfreq->devfreq) + return; + + scoped_guard(spinlock_irqsave, &rdevfreq->busy_lock) { + rocket_devfreq_update_utilisation(core); + core->busy =3D false; + } +} + +/* + * Put the clock back to the boot rate through the OPP core. Called with no + * lock held, from the last core on its way down. + */ +int rocket_devfreq_set_boot_rate(struct rocket_device *rdev) +{ + struct rocket_devfreq *rdevfreq =3D &rdev->devfreq; + int ret; + + ret =3D dev_pm_opp_set_rate(rdevfreq->owner->dev, rdev->npu_boot_rate); + if (!ret) + WRITE_ONCE(rdevfreq->cur_freq, rdev->npu_boot_rate); + + return ret; +} + +/* + * System suspend, called from every core's ->suspend before it is forced = down. + * The first one to get here does the work and the rest are no-ops. + * + * It has to be every core and not just the one that owns the devfreq devi= ce. + * That core is suspended last, and a suspend that aborts partway - a pend= ing + * wakeup, or this driver's own -EBUSY on a core that is still busy - would + * never reach it, leaving the clock raised over already gated islands. + * + * No governor tick can be in flight here: dpm_suspend() calls devfreq_sus= pend() + * before it walks the devices, which stops the monitor on every registered + * devfreq. That is also where this driver does get devfreq_suspend_device= () - + * from the PM core, on a path that holds no runtime PM lock of ours. + */ +void rocket_devfreq_suspend(struct rocket_device *rdev) +{ + struct rocket_devfreq *rdevfreq =3D &rdev->devfreq; + + if (!rdevfreq->devfreq) + return; + + guard(mutex)(&rdevfreq->lock); + + if (!rdevfreq->cores_held) + return; + + rocket_devfreq_set_rate(rdev, rdev->npu_boot_rate); + rdevfreq->cores_held =3D false; + rocket_devfreq_release_all(rdev); +} + +int rocket_devfreq_init(struct rocket_device *rdev) +{ + static const char * const clk_names[] =3D { "npu", NULL }; + static const char * const supplies[] =3D { "npu", NULL }; + struct dev_pm_opp_config config =3D { + .clk_names =3D clk_names, + .regulator_names =3D supplies, + }; + struct rocket_devfreq *rdevfreq =3D &rdev->devfreq; + struct rocket_core *owner =3D NULL; + struct dev_pm_opp *opp; + struct device *dev; + unsigned long freq; + unsigned int i; + int ret; + + /* + * Find the core the OPP table is attached to. This has to come from + * the devicetree and not from core->index, because the index is handed + * out in probe order, which is not the order the cores are written in. + */ + for (i =3D 0; i < rdev->num_cores; i++) { + if (of_property_present(rdev->cores[i].dev->of_node, + "operating-points-v2")) { + owner =3D &rdev->cores[i]; + break; + } + } + + /* + * No OPP table is not an error. It asks for the NPU to stay at the + * rate it booted at, which is what this driver did before devfreq. + */ + if (!owner) + return 0; + + dev =3D owner->dev; + + /* + * None of this can be devres. It is attached to the core that carries + * the OPP table, while the device being probed right now is whichever + * core happened to bind last, so devres would outlive the teardown in + * rocket_devfreq_fini() and a later rebind would find the OPP table + * still populated. + * + * Naming the clock matters as much as claiming the supply: without it + * the OPP core takes the first clock in the node, which is the bus + * clock, and would scale that instead of the compute clock. + */ + ret =3D dev_pm_opp_set_config(dev, &config); + if (ret < 0) { + if (ret !=3D -ENODEV) + return dev_err_probe(dev, ret, + "failed to set the OPP clock and supply\n"); + + dev_info(dev, "no NPU supply described, leaving the clock alone\n"); + return 0; + } + rdevfreq->opp_token =3D ret; + + ret =3D dev_pm_opp_of_add_table(dev); + if (ret) { + if (ret !=3D -ENODEV) + dev_err_probe(dev, ret, "failed to add the OPP table\n"); + else + ret =3D 0; + + goto err_clear_config; + } + + mutex_init(&rdevfreq->lock); + spin_lock_init(&rdevfreq->busy_lock); + + for (i =3D 0; i < rdev->num_cores; i++) + rdev->cores[i].time_last_update =3D ktime_get(); + + freq =3D rdev->npu_boot_rate; + opp =3D devfreq_recommended_opp(dev, &freq, 0); + if (IS_ERR(opp)) { + ret =3D dev_err_probe(dev, PTR_ERR(opp), + "no OPP covers the %lu Hz boot rate\n", + rdev->npu_boot_rate); + goto err_remove_table; + } + + /* + * Program the supply for the rate the NPU is already running, so that + * the regulator is not switched off underneath it by + * regulator_late_cleanup(). + */ + ret =3D dev_pm_opp_set_opp(dev, opp); + dev_pm_opp_put(opp); + if (ret) { + dev_err_probe(dev, ret, "failed to set the initial OPP\n"); + goto err_remove_table; + } + + rdevfreq->cur_freq =3D freq; + rdevfreq->owner =3D owner; + rocket_devfreq_profile.initial_freq =3D freq; + + /* + * A starting point taken from the other accelerators in tree, not a + * measurement: inference workloads have not been profiled against + * these thresholds. + */ + rdevfreq->gov_data.upthreshold =3D 50; + rdevfreq->gov_data.downdifferential =3D 10; + + rdevfreq->devfreq =3D devfreq_add_device(dev, &rocket_devfreq_profile, + DEVFREQ_GOV_SIMPLE_ONDEMAND, + &rdevfreq->gov_data); + if (IS_ERR(rdevfreq->devfreq)) { + ret =3D PTR_ERR(rdevfreq->devfreq); + rdevfreq->devfreq =3D NULL; + rdevfreq->owner =3D NULL; + + dev_err_probe(dev, ret, "failed to add the devfreq device\n"); + goto err_remove_table; + } + + return 0; + +err_remove_table: + mutex_destroy(&rdevfreq->lock); + dev_pm_opp_of_remove_table(dev); +err_clear_config: + dev_pm_opp_clear_config(rdevfreq->opp_token); + rdevfreq->opp_token =3D 0; + + return ret; +} + +void rocket_devfreq_fini(struct rocket_device *rdev) +{ + struct rocket_devfreq *rdevfreq =3D &rdev->devfreq; + struct device *dev; + + if (!rdevfreq->devfreq) + return; + + dev =3D rdevfreq->owner->dev; + + devfreq_remove_device(rdevfreq->devfreq); + rdevfreq->devfreq =3D NULL; + + /* + * Lower the clock before letting go of the cores, not after: a core + * that suspends while the clock is still raised would be unable to + * come back. + */ + scoped_guard(mutex, &rdevfreq->lock) { + if (rdevfreq->cores_held) { + rocket_devfreq_set_rate(rdev, rdev->npu_boot_rate); + rdevfreq->cores_held =3D false; + rocket_devfreq_release_all(rdev); + } + } + + /* + * Undo the OPP setup by hand, in the reverse order. Everything above + * lives on the owning core rather than on the device that was probing + * when it was set up, so nothing here is released by devres; leaving + * the table populated would make the next bind fail with the OPP core + * complaining that it is not empty. After this the owner is gone, so + * anything that still wants the boot rate asks the clock directly. + */ + rdevfreq->owner =3D NULL; + mutex_destroy(&rdevfreq->lock); + dev_pm_opp_of_remove_table(dev); + dev_pm_opp_clear_config(rdevfreq->opp_token); + rdevfreq->opp_token =3D 0; +} diff --git a/drivers/accel/rocket/rocket_devfreq.h b/drivers/accel/rocket/r= ocket_devfreq.h new file mode 100644 index 0000000000000..bdf8e89ed3761 --- /dev/null +++ b/drivers/accel/rocket/rocket_devfreq.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2025 Igor Paunovic */ + +#ifndef __ROCKET_DEVFREQ_H__ +#define __ROCKET_DEVFREQ_H__ + +#include +#include +#include + +struct rocket_core; +struct rocket_device; + +struct rocket_devfreq { + struct devfreq *devfreq; + struct devfreq_simple_ondemand_data gov_data; + + /* + * The core the OPP table is attached to, and so the one the devfreq + * device hangs off. NULL when the devicetree describes no OPP table. + */ + struct rocket_core *owner; + + /* + * The OPP clock and supply configuration is attached to the owning + * core, which is not the device this driver is probing when it is set + * up, so it cannot be devres. Zero means nothing is attached. + */ + int opp_token; + + /* + * Serialises rate changes against each other and against the set of + * runtime PM references taken below. + * + * This is never taken from a runtime PM callback. A rate change + * resumes every core while holding it, so a core that took it on its + * way down would wait for a rate change that is waiting for that same + * core to finish suspending. + */ + struct mutex lock; + unsigned long cur_freq; + bool cores_held; + + /* Guards the utilisation fields of every core. */ + spinlock_t busy_lock; +}; + +int rocket_devfreq_init(struct rocket_device *rdev); +void rocket_devfreq_fini(struct rocket_device *rdev); +void rocket_devfreq_suspend(struct rocket_device *rdev); +int rocket_devfreq_set_boot_rate(struct rocket_device *rdev); +void rocket_devfreq_record_busy(struct rocket_core *core); +void rocket_devfreq_record_idle(struct rocket_core *core); + +#endif /* __ROCKET_DEVFREQ_H__ */ diff --git a/drivers/accel/rocket/rocket_device.h b/drivers/accel/rocket/ro= cket_device.h index 466ebc4c26a8a..979ef4685443f 100644 --- a/drivers/accel/rocket/rocket_device.h +++ b/drivers/accel/rocket/rocket_device.h @@ -11,6 +11,7 @@ #include =20 #include "rocket_core.h" +#include "rocket_devfreq.h" =20 struct rocket_device { struct drm_device ddev; @@ -30,6 +31,8 @@ struct rocket_device { struct clk *npu_clk; unsigned long npu_boot_rate; atomic_t active_cores; + + struct rocket_devfreq devfreq; }; =20 struct rocket_device *rocket_device_init(struct platform_device *pdev, diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocke= t_drv.c index b7199de57ccc7..779e1cb48c241 100644 --- a/drivers/accel/rocket/rocket_drv.c +++ b/drivers/accel/rocket/rocket_drv.c @@ -14,6 +14,7 @@ #include =20 #include "rocket_device.h" +#include "rocket_devfreq.h" #include "rocket_drv.h" #include "rocket_gem.h" #include "rocket_job.h" @@ -181,15 +182,33 @@ static int rocket_probe(struct platform_device *pdev) rdev->num_cores++; =20 ret =3D rocket_core_init(&rdev->cores[core]); - if (ret) { - rdev->num_cores--; - - if (rdev->num_cores =3D=3D 0) { - rocket_device_fini(rdev); - rdev =3D NULL; + if (ret) + goto err_core; + + /* + * Every core described in the devicetree has to be bound before the + * devfreq device goes up. A rate change resumes all of them and keeps + * them resumed, and a core that had not probed yet would come up later + * underneath a raised clock. + */ + if (rdev->num_cores =3D=3D rdev->max_cores) { + ret =3D rocket_devfreq_init(rdev); + if (ret) { + rocket_core_fini(&rdev->cores[core]); + goto err_core; } } =20 + return 0; + +err_core: + rdev->num_cores--; + + if (rdev->num_cores =3D=3D 0) { + rocket_device_fini(rdev); + rdev =3D NULL; + } + return ret; } =20 @@ -203,6 +222,9 @@ static void rocket_remove(struct platform_device *pdev) if (core < 0) return; =20 + /* The devfreq device drives every core, so it goes before any of them. */ + rocket_devfreq_fini(rdev); + rocket_core_fini(&rdev->cores[core]); rdev->num_cores--; =20 @@ -246,7 +268,18 @@ static void rocket_npu_restore_boot_rate(struct rocket= _device *rdev) if (!rdev->npu_clk) return; =20 - err =3D clk_set_rate(rdev->npu_clk, rdev->npu_boot_rate); + /* + * Go through the OPP core once there is a table, never behind its + * back: it caches the OPP it last applied and skips a request for that + * same OPP, so a raw clk_set_rate() here would make the next request + * for the raised rate a silent no-op, with sysfs reporting a rate the + * hardware was not running. + */ + if (rdev->devfreq.owner) + err =3D rocket_devfreq_set_boot_rate(rdev); + else + err =3D clk_set_rate(rdev->npu_clk, rdev->npu_boot_rate); + if (err) dev_warn(rdev->cores[0].dev, "failed to restore the NPU boot rate of %lu Hz: %d\n", @@ -292,21 +325,44 @@ static int rocket_device_runtime_suspend(struct devic= e *dev) return 0; } =20 +static int rocket_device_suspend(struct device *dev) +{ + struct rocket_device *rdev =3D dev_get_drvdata(dev); + + /* + * pm_runtime_force_suspend() below powers this core down whatever the + * runtime PM usage count says, so the references taken while the clock + * is raised do not hold it off. Put the rate back first; the call is a + * no-op on every core after the first. + */ + rocket_devfreq_suspend(rdev); + + return pm_runtime_force_suspend(dev); +} + EXPORT_GPL_DEV_PM_OPS(rocket_pm_ops) =3D { RUNTIME_PM_OPS(rocket_device_runtime_suspend, rocket_device_runtime_resum= e, NULL) - SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SYSTEM_SLEEP_PM_OPS(rocket_device_suspend, pm_runtime_force_resume) }; =20 /* - * A kexec or a reboot hands the next kernel whatever rate is set here, and - * that kernel will power the islands up before it looks at the clock. + * A kexec or a reboot hands the next kernel whatever rate is set here, an= d that + * kernel will power the islands up before it looks at the clock. + * + * Take the devfreq device down before restoring the rate rather than afte= r. + * Nothing freezes workqueues on this path, so a governor tick that landed + * after the restore would raise the clock straight back up and hand on ex= actly + * what this is here to prevent. */ static void rocket_shutdown(struct platform_device *pdev) { struct rocket_device *rdev =3D dev_get_drvdata(&pdev->dev); =20 - if (rdev) - rocket_npu_restore_boot_rate(rdev); + if (!rdev) + return; + + rocket_devfreq_fini(rdev); + rocket_npu_restore_boot_rate(rdev); } =20 static struct platform_driver rocket_driver =3D { diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocke= t_job.c index 3141f210fcd1b..2ba03d85b598e 100644 --- a/drivers/accel/rocket/rocket_job.c +++ b/drivers/accel/rocket/rocket_job.c @@ -15,6 +15,7 @@ =20 #include "rocket_core.h" #include "rocket_device.h" +#include "rocket_devfreq.h" #include "rocket_drv.h" #include "rocket_job.h" #include "rocket_registers.h" @@ -151,6 +152,8 @@ static void rocket_job_hw_submit(struct rocket_core *co= re, struct rocket_job *jo =20 rocket_pc_writel(core, OPERATION_ENABLE, PC_OPERATION_ENABLE_OP_EN(1)); =20 + rocket_devfreq_record_busy(core); + dev_dbg(core->dev, "Submitted regcmd at 0x%llx to core %d", task->regcmd,= core->index); } =20 @@ -348,6 +351,8 @@ static void rocket_job_handle_irq(struct rocket_core *c= ore) rocket_pc_writel(core, OPERATION_ENABLE, 0x0); rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff); =20 + rocket_devfreq_record_idle(core); + scoped_guard(mutex, &core->job_lock) if (core->in_flight_job) { if (core->in_flight_job->next_task_idx < core->in_flight_job->task_coun= t) { @@ -379,6 +384,8 @@ rocket_reset(struct rocket_core *core, struct drm_sched= _job *bad) if (core->in_flight_job) pm_runtime_put_noidle(core->dev); =20 + rocket_devfreq_record_idle(core); + iommu_detach_group(NULL, core->iommu_group); =20 core->in_flight_job =3D NULL; --=20 2.43.0 From nobody Sun Sep 27 05:47:53 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (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 BF680331A53 for ; Fri, 4 Sep 2026 13:09:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527377; cv=none; b=n18+U5cTvU39OpmG8XdB6Yhj9JiMdsfPWSj3cxnYxgK9LMkUkBANlg2+Rd2aDTd/50oBWcuOiJx65XrI/zb/KKFrPU0VL0A97FVsZh4AsspyhPs5Gr1LO4AcCIaNa8aW76jeMOnlZpE6AJ9NJQn+5TZ2OCRllqb/QDbHf1l7OYQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527377; c=relaxed/simple; bh=nsUn/gyvK5xq/EZQV3uYgBkYE5FmY2nAUyyxBMr7d1M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=czeF5o04dEP71l0YYi3ybbY2jQzBgLeFCrcQShMvh0BhBppfs8wYcvyHIcEiTlGZTZ7sF6KrWURPBkKPsjm0uEwMyCY3d0vAaB9w0tVSo3rJgCx3j2PmIqY8lPzcsr4zvkjVY/d0WH2TcHQ4InVmAHUyDQsHgvPa80eypwQpUKM= 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=KOPc0Yuh; arc=none smtp.client-ip=209.85.128.43 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="KOPc0Yuh" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-4957799b92fso428505e9.1 for ; Fri, 04 Sep 2026 06:09:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788527374; x=1789132174; 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=G+f52AKxDAlw/HpKk7FcOocDLK6B8oOeUPjxL8dkr0A=; b=KOPc0YuhHXuaxTYSgYikUG3GNSRVZd8xLi+A5PhhQcLWWpbfcRA6A1J8ZD2Kcq+QYc 4oNuIg7cPBNHlctDUduQT8hCA0/IKlKqJjtS5/fJzftXRVhJHCnXpUdApzh/QuAqzxpk Pc0osIkXiw1hq6MnkVZ9vaWRpqA7Td9/KLmBxh3nekpjXZfcxi3ohemy7GO2ocSR4Wzy KRVXVWlyvooQrSaFKeaiTh/WlEIhULrlQa11nQfZRdJGcFdWK48eDH/NH1hp4JgnqGfJ AmI1SaKv1/0EozvbN7lBdKTKS/DJqCX67BvojSVu2HAOng9QoEeicVSPtoxCbBmrjkZM wtjw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788527374; x=1789132174; 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=G+f52AKxDAlw/HpKk7FcOocDLK6B8oOeUPjxL8dkr0A=; b=tH6TB6D3ylMK6r7QFNl/dov5eTLt+lPsm7bT2pO5q2+1tPadEOmZH9vD1mSgp8RqAU l0BNNnM4LcBuixcrZvVW31F6CwPDOOC6N4DJdPqiN6gP1MFZLTf+KTRu/Xwg21e1Njl4 xo/G+yPMPHRrQEmtqYOuVlsKDlywK4I9Fvkl00gNZ6U7bWDrcU4b5TEjDakHM0ZOSnY6 wJSbLQYccd5JZUkbwQS/l+nY3bM8RMy4O+qe8vBtJGd/XRZacNypmy1YGPpTV0zmamVA aDEbxDilcdWrwFrqWW1q2jZsii/yT6JVcw4q0J4JOevmSbtoEUVMR8s7fm8LHDXQBLeD NULw== X-Forwarded-Encrypted: i=1; AKwUvBxSp71Zfra1nfB09pqwyv2gHtWpUjr6h3t7uXXf/B2jcI6Po/Sn2boA+l34t6eUQmnAeY6zIvDur20uyPQ=@vger.kernel.org X-Gm-Message-State: AFuF++n04ClF64R8gduOnmqmGba2HKcfm389iYia7FhBjHBX3HLx9y8O aoKRjtjNX0XKHU3iPNiYeVhFXfRwi0SjB/ZltmLr/3TsZgMLUhu51mD/ X-Gm-Gg: AYBFou2tXqQ2F14y2dd2tge5PxymlFJdgvXdXPo54s4zV+rAnFw6jCK91OMlyfJQemj 7HrEzGSGc+lsKxoNEhxsk1Pw9b28PgxBa2gd2zHBboGCnJYacrXCGQz/szHwBgeLtkYnpjUReQu XV71me49WQ92oPvEA2xM/TTGF0xf9gvaQSsxd3HSUTvKw/I8x9jJEmGx/lTwi4TTWpN/Nx0czC2 4laUNF6wkW7fJ7cvTAkOT/Ye0VbKMsv9DYc/O/80cJZA3dgdPOQjRWSoUImL8q4mXyEt2pxerqB wX9cFAga9f0lrpBNDM9IGnitqwxPgEr5eHUy6RGMpPBQ3LpMXhD7A0eyAFFUElZuFXbtoSbDZZM sZjt/efUUrlowg+fpET6b3PTZ6NpXX1KRGHzZlcWwmcNGYZeiKIF54tBKvIh3n48HZpRtIcuz0D UAKX81xnumyBHwMgdu/ubd+NxZR7qeIqIZuq8pplUXHj4/np2UFRlNxV6IuTbbhXLuYA7L1lwQn ypPRl4ArygdyMR686UCo5lN2DPWX7HC5j5zb9lrBvHaZByI/BF96g2p7gffBm9TVJDVm/ibGurP yJoo X-Received: by 2002:a05:600c:a40e:b0:49c:f9b8:bae0 with SMTP id 5b1f17b1804b1-49cf9b8baffmr34000785e9.2.1788527373810; Fri, 04 Sep 2026 06:09:33 -0700 (PDT) Received: from OrangePi5-Plus.BB-HOME (20014C4E1B871500CB6EF488A18F9C42.dsl.pool.telekom.hu. [2001:4c4e:1b87:1500:cb6e:f488:a18f:9c42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49ce554d52esm135575435e9.3.2026.09.04.06.09.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 06:09:33 -0700 (PDT) From: Igor Paunovic To: Tomeu Vizoso , Oded Gabbay , Heiko Stuebner Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sidong Yang , Diederik de Haas , Sebastian Reichel , Jiaxing Hu , Nicolas Dufresne , Jonas Karlman , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Igor Paunovic Subject: [PATCH 6/7] accel/rocket: register a devfreq cooling device Date: Fri, 4 Sep 2026 15:08:57 +0200 Message-ID: <20260904130858.27803-7-royalnet026@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260904130858.27803-1-royalnet026@gmail.com> References: <20260904130858.27803-1-royalnet026@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" With devfreq driving the NPU clock, a thermal zone can now throttle the NPU by capping that clock. Register the cooling device so a devicetree can bind it to a zone. The _em variant is used, not because there is an energy model today but so that there will be one the day a power coefficient for this NPU is measured. There is none now: the NPU node carries no dynamic-power-coefficient, Rockchip does not publish one, and a made-up number would be worse than no number. devfreq_cooling_em_register() logs the missing model at debug level and registers the cooling device anyway, so what this gets today is step-wise throttling with no power model for the IPA governor to use. Measuring the coefficient is follow-up work. Registration is allowed to fail. A kernel built without DEVFREQ_THERMAL gets a stub that returns an error, and losing throttling is not a reason to refuse to drive the NPU at all, so the failure is logged and probe carries on. The cooling device is unregistered by hand before the devfreq device it is attached to goes away. Signed-off-by: Igor Paunovic Assisted-by: LLM sparse checkpatch --- drivers/accel/rocket/rocket_devfreq.c | 24 ++++++++++++++++++++++++ drivers/accel/rocket/rocket_devfreq.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/drivers/accel/rocket/rocket_devfreq.c b/drivers/accel/rocket/r= ocket_devfreq.c index 9d923a0b6ea30..86bc34819a187 100644 --- a/drivers/accel/rocket/rocket_devfreq.c +++ b/drivers/accel/rocket/rocket_devfreq.c @@ -3,6 +3,7 @@ =20 #include #include +#include #include #include #include @@ -404,6 +405,24 @@ int rocket_devfreq_init(struct rocket_device *rdev) goto err_remove_table; } =20 + /* + * Thermal throttling is optional, so a kernel built without + * DEVFREQ_THERMAL keeps a working NPU rather than a failed probe. + * + * The _em variant is used so that the driver is ready for an energy + * model the day a power coefficient for this NPU is measured. There is + * none today: the NPU node has no dynamic-power-coefficient, the vendor + * does not publish one, and inventing a number would be worse than + * having none. Without it the EM registration inside is skipped and + * throttling is step-wise, with no power model for IPA to use. + */ + rdevfreq->cooling =3D devfreq_cooling_em_register(rdevfreq->devfreq, NULL= ); + if (IS_ERR(rdevfreq->cooling)) { + dev_info(dev, "no devfreq cooling device (%pe), NPU will not be throttle= d\n", + rdevfreq->cooling); + rdevfreq->cooling =3D NULL; + } + return 0; =20 err_remove_table: @@ -426,6 +445,11 @@ void rocket_devfreq_fini(struct rocket_device *rdev) =20 dev =3D rdevfreq->owner->dev; =20 + if (rdevfreq->cooling) { + devfreq_cooling_unregister(rdevfreq->cooling); + rdevfreq->cooling =3D NULL; + } + devfreq_remove_device(rdevfreq->devfreq); rdevfreq->devfreq =3D NULL; =20 diff --git a/drivers/accel/rocket/rocket_devfreq.h b/drivers/accel/rocket/r= ocket_devfreq.h index bdf8e89ed3761..d5876d62a0b7c 100644 --- a/drivers/accel/rocket/rocket_devfreq.h +++ b/drivers/accel/rocket/rocket_devfreq.h @@ -10,9 +10,11 @@ =20 struct rocket_core; struct rocket_device; +struct thermal_cooling_device; =20 struct rocket_devfreq { struct devfreq *devfreq; + struct thermal_cooling_device *cooling; struct devfreq_simple_ondemand_data gov_data; =20 /* --=20 2.43.0 From nobody Sun Sep 27 05:47:53 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (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 5A57F379C44 for ; Fri, 4 Sep 2026 13:09:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527379; cv=none; b=O3zab7yy6y5taW6/aZnfQMK7fJdNnBRaEl/oMVKgEuZU842b6Ql1ejS0r93sZywqqXPjRzezun3mfeaprO+ADe6miZHtRHKNiM8RHxRzRUoUmGlHksclzt7ZBnsUAyTOARqGVEZxvy/NdbcZO4HZ8z1gyQU8GJQwkKZquQyxjwA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788527379; c=relaxed/simple; bh=nZaahBCK6Uh8ja4ZUTeoTBxF+bQS6MmWEyFJ+20Wl+E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QravLjZwXHmv/tRb3xKgiAI5Ci0DpO9Z/lEQoz4NjTBpCg4PxuDmjv4PcJ6LF97sOdIUIIID6cTYwopiJpKRuFWH4lQ0PgKaOkRNtZNtmfAqgxc1ZI5+aREBDyOxD4cPbJpA1PC+TZQktByROVDUr1cviFzAkgyUiQS/VBI/rqU= 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=B9YpSm2/; arc=none smtp.client-ip=209.85.128.53 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="B9YpSm2/" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-49987367394so461115e9.0 for ; Fri, 04 Sep 2026 06:09:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788527375; x=1789132175; 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=E/t7wxFBsuNgANZ3txwtSGvXS0Fyj90ixWLfFCZVhFw=; b=B9YpSm2/yqcKk7uaT+ocug2PvgR14o3GROHMX1cOyC9KEJE6UytY3NHeCkhifI2znq 4e52yb3DhATzoJCLscjUOvNivfbvFsEEpad04nlAPeTC5/fsL7Cp5PsRDJra8vYs7KWL ha8r2LUzGMNceoFDjWg+3fKvnwuZm1cdXMIveoNJgmX1j2FwLPb26ORbSpOLUsoVjfH3 sk5WB33SKAn3NAqeiPozFIgnz7VB+UgPeAPqdoFHEr9f3n3wEPdPC32IZ3A1SHvSVah7 XQ1sbkS3TFEA1uTL2ox/vZeWC24t2+AqgDQTQjyxhlgyZCvTFTbTaLjJKRbWmpB+Fh31 a09Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788527375; x=1789132175; 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=E/t7wxFBsuNgANZ3txwtSGvXS0Fyj90ixWLfFCZVhFw=; b=rYxhaeyTt1vtyjxkw6bp6stVzop2/0E5mvIJfrvv7A4eDCvnz2HOjMZ914AI6mwQfa zTsCROBZh84oW7WkGdS8C2FaprBCG/KTTGoDCr/o/M8ad6LiRyodNarm+y+1JqST7DqM 2P0n20JTz1DmXoj8FuRegt1f5FKxZpFo7I4iNg/MF6pS9NW18lZK1OTI6NhEsVYuBAIM zPDmVKyrY4qYfKQbRUKFV9YSBQ2py5U/YPoonBGr5yejeE2clZ7d6eCjK8DJV9xMqS21 dFK17OdY0u9WxyZ1ISAWklTmoVYjHN5eyeP2QU5wgkrsWck/M4J0kU4L3vTubtA+Tx5i AGrg== X-Forwarded-Encrypted: i=1; AKwUvByb8bwOaI/w7Noxj1krv33yAQ2jdPK7ZeRqDNG0V2if6mY41OBgFXiHwwedjhGKnMYjv+MPvStIyrGXId8=@vger.kernel.org X-Gm-Message-State: AFuF++kiwUEY737NlrgyDDnkbNmrgRHIJi6YjubLlIoQ6UXgM/QJpfmM TnEFx8W1Dwa2jtbYrr1lKIYZfKtu9/jL/n3QNOgFcCZdluwFIOkScfxAthQVzw== X-Gm-Gg: AYBFou1H8Zc+ShlEhhFiQse0KgF1SEpKpFgZw3Ice+xKAdLuc0p0akd04sO7WTq2G/2 8ubtl+j9hv0IVSfwP/i+exTqMvCyT9RE/qyBTLkTzzoXy+pQ2/EKDaVqcEzoe3YXjJX/FSuVBJh yumiY4o64+fOhkJwghKfxgDlNpAzkno5LBwCECTeyqTbqv26Yelb1X3ZI6ugaDD7GqHeJIcgr7j QvVvxuIsBeCFGaOgFR4/1SBgvaFEZ0Hr6/dnxGXcEmTWXZC2YQwq5mzsFneamXlDQH62ZAwWHMR PTZW+J8Ik5OP+zle86lSfEr0WaDlQn6P4WQwCgyagniMFw2n3nOBOIgHnWQtrpr7FVEgYnodF5w /LTld3wFQFvM2LfcKqwSqsjXHdwbVCB71qJBhO55xrDQZSCNEK4UP/DDPwCiNicHDKpRgQ/optn pB4RGey58eh5q7EkEexYnmubuH96qw2xN2arqi3JRnY4Y9UhnWHnmVHqPIwlCd24IVDmll31mBl b8xtzNqJIPFag8jPgf7ULx3m3g6TOLuKwzmZou+9iv7zaDkvb5k9lhsyKt9epFsZdMo X-Received: by 2002:a05:600c:1c24:b0:49b:9241:7ff0 with SMTP id 5b1f17b1804b1-49cf7f4d9b7mr51876155e9.0.1788527375338; Fri, 04 Sep 2026 06:09:35 -0700 (PDT) Received: from OrangePi5-Plus.BB-HOME (20014C4E1B871500CB6EF488A18F9C42.dsl.pool.telekom.hu. [2001:4c4e:1b87:1500:cb6e:f488:a18f:9c42]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49ce554d52esm135575435e9.3.2026.09.04.06.09.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Sep 2026 06:09:35 -0700 (PDT) From: Igor Paunovic To: Tomeu Vizoso , Oded Gabbay , Heiko Stuebner Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sidong Yang , Diederik de Haas , Sebastian Reichel , Jiaxing Hu , Nicolas Dufresne , Jonas Karlman , dri-devel@lists.freedesktop.org, linux-rockchip@lists.infradead.org, linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Igor Paunovic Subject: [PATCH 7/7] arm64: dts: rockchip: rk3588: add passive cooling to the NPU thermal zone Date: Fri, 4 Sep 2026 15:08:58 +0200 Message-ID: <20260904130858.27803-8-royalnet026@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260904130858.27803-1-royalnet026@gmail.com> References: <20260904130858.27803-1-royalnet026@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" The NPU zone has had only a critical trip at 115 degrees, which is a shutdown and not a cooling policy. Now that the NPU can be throttled by capping its clock, give the zone a passive trip and a cooling map, in the same shape and at the same temperatures as the GPU zone right above it: 85 degrees with 2 degrees of hysteresis, and a 100 ms passive polling delay. The #cooling-cells property goes on rknn_core_0, the core that carries the shared clock and the OPP table. The other two cores have no clock of their own and cannot be throttled independently of it. The cooling map is inert until the driver registers a cooling device: thermal_of_should_bind() only resolves a map entry once a matching cdev appears, so this patch on its own changes nothing but the trip point. The thermal path itself has not been exercised on the board this was written on. Reaching 85 degrees on an NPU workload with the fan curve here has not been possible, so what is verified is that the zone parses and binds, not that throttling engages at temperature. Signed-off-by: Igor Paunovic Assisted-by: LLM checkpatch dtbs_check --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boo= t/dts/rockchip/rk3588-base.dtsi index 376ad04e07869..c3d22b08f415b 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1162,6 +1162,7 @@ rknn_core_0: npu@fdab0000 { clock-names =3D "aclk", "hclk", "npu", "pclk"; assigned-clocks =3D <&scmi_clk SCMI_CLK_NPU>; assigned-clock-rates =3D <200000000>; + #cooling-cells =3D <2>; resets =3D <&cru SRST_A_RKNN0>, <&cru SRST_H_RKNN0>; reset-names =3D "srst_a", "srst_h"; power-domains =3D <&power RK3588_PD_NPUTOP>; @@ -3212,17 +3213,31 @@ map0 { }; =20 npu_thermal: npu-thermal { - polling-delay-passive =3D <0>; + polling-delay-passive =3D <100>; polling-delay =3D <0>; thermal-sensors =3D <&tsadc 6>; =20 trips { + npu_alert: npu-alert { + temperature =3D <85000>; + hysteresis =3D <2000>; + type =3D "passive"; + }; + npu_crit: npu-crit { temperature =3D <115000>; hysteresis =3D <0>; type =3D "critical"; }; }; + + cooling-maps { + map0 { + trip =3D <&npu_alert>; + cooling-device =3D + <&rknn_core_0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; }; }; =20 --=20 2.43.0