* [PATCH 1/4] interconnect: icc-clk: support N:1 master-to-slave topology
2026-10-07 6:50 [PATCH 0/4] Add Qualcomm's IPQ9650 APSS Clock Driver Support Kathiravan Thirumoorthy
@ 2026-10-07 6:50 ` Kathiravan Thirumoorthy
2026-10-07 7:05 ` sashiko-bot
2026-10-07 6:50 ` [PATCH 2/4] clk: qcom: clk-alpha-pll: Skip Zonda PLL configuration if already enabled Kathiravan Thirumoorthy
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Kathiravan Thirumoorthy @ 2026-10-07 6:50 UTC (permalink / raw)
To: Georgi Djakov, Bjorn Andersson, Abel Vesa, Stephen Boyd,
Brian Masney, Jerome Brunet, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-pm, linux-kernel, linux-arm-msm, linux-clk, devicetree,
Kathiravan Thirumoorthy
icc_clk_register() assumes each icc_clk_data entry describes a unique
master/slave pair and creates both nodes for every entry. When several
masters share the same slave_id, the shared slave node is created and
added again on each later entry. icc_node_create() returns the existing
node, so icc_node_add() hits WARN_ON(node->provider):
WARNING: drivers/interconnect/core.c:1103 at icc_node_add+0x10c/0x120
Call trace:
icc_node_add+0x10c/0x120 (P)
icc_clk_register+0x1a4/0x214
devm_icc_clk_register+0x18/0x80
qcom_cc_really_probe+0x4c8/0x514
apss_ipq9650_probe+0xc0/0x108
This topology is needed on IPQ9650, where the silver and gold CPU
clusters both vote on the single L3 clock through a shared SLAVE_L3
node.
The onecell table is also filled in registration order, so xlate only
works when the node IDs happen to match that order. Size the table by
the highest node ID, index it by ID, and skip slave creation when the
node is already registered.
icc_clk_set() also passes only the requesting node's bandwidth to
clk_set_rate(), so the last writer wins. A low vote from one master can
then lower the clock below what another master asked for. Before setting
the rate, take the highest peak bandwidth across all nodes that share
the clock.
Signed-off-by: Kathiravan Thirumoorthy <kathiravan.thirumoorthy@oss.qualcomm.com>
---
drivers/interconnect/icc-clk.c | 45 +++++++++++++++++++++++++++++++++++-------
1 file changed, 38 insertions(+), 7 deletions(-)
diff --git a/drivers/interconnect/icc-clk.c b/drivers/interconnect/icc-clk.c
index 93c030608d3e..567d071fade8 100644
--- a/drivers/interconnect/icc-clk.c
+++ b/drivers/interconnect/icc-clk.c
@@ -24,7 +24,10 @@ struct icc_clk_provider {
static int icc_clk_set(struct icc_node *src, struct icc_node *dst)
{
+ unsigned long rate = icc_units_to_bps(src->peak_bw);
+ struct icc_provider *provider = src->provider;
struct icc_clk_node *qn = src->data;
+ struct icc_node *node;
int ret;
if (!qn || !qn->clk)
@@ -45,7 +48,21 @@ static int icc_clk_set(struct icc_node *src, struct icc_node *dst)
qn->enabled = true;
}
- return clk_set_rate(qn->clk, icc_units_to_bps(src->peak_bw));
+ /*
+ * Multiple master nodes can share the same underlying clock (N:1
+ * topology, e.g. several CPU clusters voting on one L3 clock).
+ * Aggregate the peak bandwidth across all of them before setting
+ * the rate, otherwise the last caller wins and can undervote what
+ * another master already asked for.
+ */
+ list_for_each_entry(node, &provider->nodes, node_list) {
+ struct icc_clk_node *n = node->data;
+
+ if (n && clk_is_match(n->clk, qn->clk))
+ rate = max(rate, icc_units_to_bps(node->peak_bw));
+ }
+
+ return clk_set_rate(qn->clk, rate);
}
static int icc_clk_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
@@ -82,12 +99,17 @@ struct icc_provider *icc_clk_register(struct device *dev,
struct icc_provider *provider;
struct icc_onecell_data *onecell;
struct icc_node *node;
- int ret, i, j;
+ unsigned int max_id = 0;
+ int ret, i;
- onecell = devm_kzalloc(dev, struct_size(onecell, nodes, 2 * num_clocks), GFP_KERNEL);
+ /* Find the highest node ID to size the xlate lookup table */
+ for (i = 0; i < num_clocks; i++)
+ max_id = max(max_id, max(data[i].master_id, data[i].slave_id));
+
+ onecell = devm_kzalloc(dev, struct_size(onecell, nodes, max_id + 1), GFP_KERNEL);
if (!onecell)
return ERR_PTR(-ENOMEM);
- onecell->num_nodes = 2 * num_clocks;
+ onecell->num_nodes = max_id + 1;
qp = devm_kzalloc(dev, struct_size(qp, clocks, num_clocks), GFP_KERNEL);
if (!qp)
@@ -106,7 +128,7 @@ struct icc_provider *icc_clk_register(struct device *dev,
icc_provider_init(provider);
- for (i = 0, j = 0; i < num_clocks; i++) {
+ for (i = 0; i < num_clocks; i++) {
qp->clocks[i].clk = data[i].clk;
node = icc_node_create(first_id + data[i].master_id);
@@ -126,7 +148,16 @@ struct icc_provider *icc_clk_register(struct device *dev,
icc_node_add(node, provider);
/* link to the next node, slave */
icc_link_create(node, first_id + data[i].slave_id);
- onecell->nodes[j++] = node;
+ onecell->nodes[data[i].master_id] = node;
+
+ /*
+ * Slave node may already be registered by an earlier
+ * master in an N:1 topology (multiple masters sharing
+ * the same slave, e.g. several CPU clusters voting on
+ * one L3 clock).
+ */
+ if (onecell->nodes[data[i].slave_id])
+ continue;
node = icc_node_create(first_id + data[i].slave_id);
if (IS_ERR(node)) {
@@ -143,7 +174,7 @@ struct icc_provider *icc_clk_register(struct device *dev,
/* no data for slave node */
icc_node_add(node, provider);
- onecell->nodes[j++] = node;
+ onecell->nodes[data[i].slave_id] = node;
}
ret = icc_provider_register(provider);
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 4/4] clk: qcom: apss-ipq9650: Add IPQ9650 APSS clock controller driver
2026-10-07 6:50 [PATCH 0/4] Add Qualcomm's IPQ9650 APSS Clock Driver Support Kathiravan Thirumoorthy
` (2 preceding siblings ...)
2026-10-07 6:50 ` [PATCH 3/4] dt-bindings: clock: ipq9650-apss-clk: Add IPQ9650 apss clock controller Kathiravan Thirumoorthy
@ 2026-10-07 6:50 ` Kathiravan Thirumoorthy
3 siblings, 0 replies; 7+ messages in thread
From: Kathiravan Thirumoorthy @ 2026-10-07 6:50 UTC (permalink / raw)
To: Georgi Djakov, Bjorn Andersson, Abel Vesa, Stephen Boyd,
Brian Masney, Jerome Brunet, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-pm, linux-kernel, linux-arm-msm, linux-clk, devicetree,
Kathiravan Thirumoorthy
The APSS clock controller on IPQ9650 provides the CPU and L3 clocks.
The four Cortex-A55 cores (silver), the Cortex-A78 core (gold) and the
L3 are each clocked by their own Zonda PLL. Each clock is selected
through a glitch-free mux (GFM) whose parents are XO, GPLL0 and the
corresponding PLL. The gold mux can also select the PLL's post-divider
output.
The L3 needs to be scaled along with the CPUs, so register an icc-clk
provider in which both CPU clusters vote on the L3 core clock.
Signed-off-by: Kathiravan Thirumoorthy <kathiravan.thirumoorthy@oss.qualcomm.com>
---
drivers/clk/qcom/Kconfig | 9 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/apss-ipq9650.c | 385 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 395 insertions(+)
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index 27d0ab24d50a..a17f661c3102 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -599,6 +599,15 @@ config IPQ_APSS_6018
Say Y if you want to support CPU frequency scaling on
ipq based devices.
+config IPQ_APSS_9650
+ tristate "IPQ9650 APSS Clock Controller"
+ depends on ARM64 || COMPILE_TEST
+ default y if IPQ_GCC_9650
+ help
+ Support for APSS Clock controller on Qualcomm IPQ9650 platform.
+ Say Y if you want to support CPU frequency scaling on IPQ9650 based
+ devices.
+
config IPQ_CMN_PLL
tristate "IPQ CMN PLL Clock Controller"
depends on ARM64 || COMPILE_TEST
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 6affb5073f26..ec3d8512a812 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_CLK_QCM2290_GPUCC) += gpucc-qcm2290.o
obj-$(CONFIG_IPQ_APSS_PLL) += apss-ipq-pll.o
obj-$(CONFIG_IPQ_APSS_5424) += apss-ipq5424.o
obj-$(CONFIG_IPQ_APSS_6018) += apss-ipq6018.o
+obj-$(CONFIG_IPQ_APSS_9650) += apss-ipq9650.o
obj-$(CONFIG_IPQ_CMN_PLL) += ipq-cmn-pll.o
obj-$(CONFIG_IPQ_GCC_4019) += gcc-ipq4019.o
obj-$(CONFIG_IPQ_GCC_5018) += gcc-ipq5018.o
diff --git a/drivers/clk/qcom/apss-ipq9650.c b/drivers/clk/qcom/apss-ipq9650.c
new file mode 100644
index 000000000000..7602f3896fe3
--- /dev/null
+++ b/drivers/clk/qcom/apss-ipq9650.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/interconnect-provider.h>
+
+#include <dt-bindings/clock/qcom,apss-ipq.h>
+#include <dt-bindings/interconnect/qcom,ipq9650.h>
+
+#include "clk-alpha-pll.h"
+#include "clk-branch.h"
+#include "clk-regmap.h"
+#include "common.h"
+
+enum {
+ DT_XO,
+ DT_GPLL0,
+};
+
+static const struct pll_vco silver_pll_vco[] = {
+ { 816000000, 1968000000, 0 },
+};
+
+static const struct pll_vco gold_pll_vco[] = {
+ { 696000000, 1992000000, 0 },
+};
+
+static const struct pll_vco l3_pll_vco[] = {
+ { 696000000, 1512000000, 0 },
+};
+
+static const struct alpha_pll_config silver_apss_pll_config = {
+ .l = 0x23,
+ .config_ctl_val = 0x08200920,
+ .config_ctl_hi_val = 0x05008001,
+ .config_ctl_hi1_val = 0x04000000,
+ .user_ctl_val = 0x01000009,
+};
+
+static const struct alpha_pll_config gold_apss_pll_config = {
+ .l = 0x26,
+ .config_ctl_val = 0x08200920,
+ .config_ctl_hi_val = 0x05008001,
+ .config_ctl_hi1_val = 0x04000000,
+ .user_ctl_val = 0x01000009,
+};
+
+static const struct alpha_pll_config l3_pll_config = {
+ .l = 0x1e,
+ .config_ctl_val = 0x08200920,
+ .config_ctl_hi_val = 0x05008001,
+ .config_ctl_hi1_val = 0x04000000,
+ .user_ctl_val = 0x01000009,
+};
+
+static struct clk_alpha_pll silver_apss_pll = {
+ .offset = 0x0,
+ .config = &silver_apss_pll_config,
+ .vco_table = silver_pll_vco,
+ .num_vco = ARRAY_SIZE(silver_pll_vco),
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+ .flags = SUPPORTS_DYNAMIC_UPDATE,
+ .clkr = {
+ .enable_reg = 0x0,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "silver_apss_pll",
+ .parent_data = &(const struct clk_parent_data){
+ .index = DT_XO,
+ },
+ .num_parents = 1,
+ .flags = CLK_IS_CRITICAL,
+ .ops = &clk_alpha_pll_zonda_ops,
+ },
+ },
+};
+
+static struct clk_alpha_pll gold_apss_pll = {
+ .offset = 0x10000,
+ .config = &gold_apss_pll_config,
+ .vco_table = gold_pll_vco,
+ .num_vco = ARRAY_SIZE(gold_pll_vco),
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+ .flags = SUPPORTS_DYNAMIC_UPDATE,
+ .clkr = {
+ .enable_reg = 0x10000,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gold_apss_pll",
+ .parent_data = &(const struct clk_parent_data){
+ .index = DT_XO,
+ },
+ .num_parents = 1,
+ .flags = CLK_IS_CRITICAL,
+ .ops = &clk_alpha_pll_zonda_ops,
+ },
+ },
+};
+
+static const struct clk_div_table gold_apss_pll_post_div_table[] = {
+ { 0x0, 1 },
+ { 0x1, 2 },
+ { }
+};
+
+static struct clk_alpha_pll_postdiv gold_apss_pll_postdiv = {
+ .offset = 0x10000,
+ .post_div_shift = 8,
+ .post_div_table = gold_apss_pll_post_div_table,
+ .num_post_div = ARRAY_SIZE(gold_apss_pll_post_div_table),
+ .width = 2,
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gold_apss_pll_postdiv",
+ .parent_hws = (const struct clk_hw*[]){
+ &gold_apss_pll.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_alpha_pll_postdiv_zonda_ops,
+ },
+};
+
+static struct clk_alpha_pll l3_pll = {
+ .offset = 0x20000,
+ .config = &l3_pll_config,
+ .vco_table = l3_pll_vco,
+ .num_vco = ARRAY_SIZE(l3_pll_vco),
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_ZONDA],
+ .flags = SUPPORTS_DYNAMIC_UPDATE,
+ .clkr = {
+ .enable_reg = 0x20000,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "l3_pll",
+ .parent_data = &(const struct clk_parent_data){
+ .index = DT_XO,
+ },
+ .num_parents = 1,
+ .flags = CLK_IS_CRITICAL,
+ .ops = &clk_alpha_pll_zonda_ops,
+ },
+ },
+};
+
+struct clk_gfm {
+ u32 mux_reg;
+ u32 mux_mask;
+ struct clk_hw hw;
+ void __iomem *base;
+};
+
+#define to_clk_gfm(_hw) container_of(_hw, struct clk_gfm, hw)
+
+static u8 clk_gfm_get_parent(struct clk_hw *hw)
+{
+ struct clk_gfm *gfm = to_clk_gfm(hw);
+ u32 val;
+
+ val = readl(gfm->base + gfm->mux_reg);
+ return val & gfm->mux_mask;
+}
+
+static int clk_gfm_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_gfm *gfm = to_clk_gfm(hw);
+ u32 val;
+
+ val = readl(gfm->base + gfm->mux_reg);
+ val &= ~gfm->mux_mask;
+ val |= (index & gfm->mux_mask);
+ writel(val, gfm->base + gfm->mux_reg);
+
+ return 0;
+}
+
+static const struct clk_ops clk_gfm_ops = {
+ .get_parent = clk_gfm_get_parent,
+ .set_parent = clk_gfm_set_parent,
+ .determine_rate = __clk_mux_determine_rate_closest,
+};
+
+static struct clk_gfm silver_apss_pll_gfmux = {
+ .mux_reg = 0x84,
+ .mux_mask = 0xf,
+ .hw.init = &(struct clk_init_data) {
+ .name = "silver_apss_pll_gfmux",
+ .parent_data = (const struct clk_parent_data[]) {
+ { .index = DT_XO },
+ { .index = DT_GPLL0 },
+ { .hw = &silver_apss_pll.clkr.hw },
+ },
+ .num_parents = 3,
+ .ops = &clk_gfm_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_gfm gold_apss_pll_gfmux = {
+ .mux_reg = 0x10084,
+ .mux_mask = 0xf,
+ .hw.init = &(struct clk_init_data) {
+ .name = "gold_apss_pll_gfmux",
+ .parent_data = (const struct clk_parent_data[]) {
+ { .index = DT_XO },
+ { .index = DT_GPLL0 },
+ { .hw = &gold_apss_pll.clkr.hw },
+ { .hw = &gold_apss_pll_postdiv.clkr.hw },
+ },
+ .num_parents = 4,
+ .ops = &clk_gfm_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_gfm l3_pll_gfmux = {
+ .mux_reg = 0x20084,
+ .mux_mask = 0xf,
+ .hw.init = &(struct clk_init_data) {
+ .name = "l3_pll_gfmux",
+ .parent_data = (const struct clk_parent_data[]) {
+ { .index = DT_XO },
+ { .index = DT_GPLL0 },
+ { .hw = &l3_pll.clkr.hw },
+ },
+ .num_parents = 3,
+ .ops = &clk_gfm_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+};
+
+static struct clk_branch apss_silver_core_clk = {
+ .halt_reg = 0x8c,
+ .clkr = {
+ .enable_reg = 0x8c,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data) {
+ .name = "apss_silver_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &silver_apss_pll_gfmux.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch apss_gold_core_clk = {
+ .halt_reg = 0x1008c,
+ .clkr = {
+ .enable_reg = 0x1008c,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data) {
+ .name = "apss_gold_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gold_apss_pll_gfmux.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch l3_core_clk = {
+ .halt_reg = 0x2008c,
+ .clkr = {
+ .enable_reg = 0x2008c,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data) {
+ .name = "l3_core_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &l3_pll_gfmux.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_hw *apss_hws[] = {
+ &silver_apss_pll_gfmux.hw,
+ &l3_pll_gfmux.hw,
+ &gold_apss_pll_gfmux.hw,
+};
+
+static struct clk_regmap *apss_clks[] = {
+ [APSS_PLL_EARLY] = &silver_apss_pll.clkr,
+ [APSS_SILVER_CORE_CLK] = &apss_silver_core_clk.clkr,
+ [L3_PLL] = &l3_pll.clkr,
+ [L3_CORE_CLK] = &l3_core_clk.clkr,
+ [APSS_GOLD_PLL] = &gold_apss_pll.clkr,
+ [APSS_GOLD_PLL_POSTDIV] = &gold_apss_pll_postdiv.clkr,
+ [APSS_GOLD_CORE_CLK] = &apss_gold_core_clk.clkr,
+};
+
+static const struct regmap_config apss_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x2fffc,
+ .fast_io = true,
+};
+
+static struct clk_alpha_pll *ipq9650_apss_plls[] = {
+ &silver_apss_pll,
+ &gold_apss_pll,
+ &l3_pll,
+};
+
+static const struct qcom_cc_driver_data ipq9650_apss_driver_data = {
+ .alpha_plls = ipq9650_apss_plls,
+ .num_alpha_plls = ARRAY_SIZE(ipq9650_apss_plls),
+};
+
+#define IPQ_APPS_PLL_ID (9650 * 3) /* some unique value */
+
+static const struct qcom_icc_hws_data icc_cpu_l3[] = {
+ { MASTER_CPU_SILVER, SLAVE_L3, L3_CORE_CLK },
+ { MASTER_CPU_GOLD, SLAVE_L3, L3_CORE_CLK },
+};
+
+static const struct qcom_cc_desc apss_desc = {
+ .config = &apss_regmap_config,
+ .clks = apss_clks,
+ .num_clks = ARRAY_SIZE(apss_clks),
+ .icc_hws = icc_cpu_l3,
+ .num_icc_hws = ARRAY_SIZE(icc_cpu_l3),
+ .icc_first_node_id = IPQ_APPS_PLL_ID,
+ .clk_hws = apss_hws,
+ .num_clk_hws = ARRAY_SIZE(apss_hws),
+ .driver_data = &ipq9650_apss_driver_data,
+};
+
+static int apss_ipq9650_probe(struct platform_device *pdev)
+{
+ const struct qcom_cc_desc *desc = &apss_desc;
+ struct regmap *regmap;
+ struct clk_gfm *gfm;
+ void __iomem *base;
+ int i;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &apss_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ for (i = 0; i < desc->num_clk_hws; i++) {
+ gfm = to_clk_gfm(apss_hws[i]);
+ gfm->base = base;
+ }
+
+ return qcom_cc_really_probe(&pdev->dev, desc, regmap);
+}
+
+static const struct of_device_id apss_ipq9650_match_table[] = {
+ { .compatible = "qcom,ipq9650-apss-clk" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, apss_ipq9650_match_table);
+
+static struct platform_driver apss_ipq9650_driver = {
+ .probe = apss_ipq9650_probe,
+ .driver = {
+ .name = "qcom,ipq9650-apss-clk",
+ .of_match_table = apss_ipq9650_match_table,
+ .sync_state = icc_sync_state,
+ },
+};
+
+module_platform_driver(apss_ipq9650_driver);
+
+MODULE_DESCRIPTION("Qualcomm IPQ9650 APSS Clock Driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread