From 935ad6242c47b37380d0cb7ec366516fe11855b4 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 24 Jun 2026 15:23:55 +0900 Subject: [PATCH 01/45] clk: mediatek: mt6735: Unregister PLLs on probe failure mtk_clk_register_plls() registers the apmixedsys PLL clocks manually, while clk_mt6735_apmixed_remove() unregisters them on driver removal. If devm_of_clk_add_hw_provider() fails after the PLL registration succeeds, probe returns the error directly and the remove callback is not run. This leaves the registered PLL clocks behind on the probe failure path. Unregister the PLLs in that failure branch before returning the error. Fixes: 43c04ed79189 ("clk: mediatek: Add drivers for MediaTek MT6735 main clock and reset drivers") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt6735-apmixedsys.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/clk/mediatek/clk-mt6735-apmixedsys.c b/drivers/clk/mediatek/clk-mt6735-apmixedsys.c index 9e30c089a209..b6eb6a581c31 100644 --- a/drivers/clk/mediatek/clk-mt6735-apmixedsys.c +++ b/drivers/clk/mediatek/clk-mt6735-apmixedsys.c @@ -102,9 +102,12 @@ static int clk_mt6735_apmixed_probe(struct platform_device *pdev) ret = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_onecell_get, clk_data); - if (ret) + if (ret) { dev_err(&pdev->dev, "Failed to register clock provider: %d\n", ret); + mtk_clk_unregister_plls(apmixedsys_plls, ARRAY_SIZE(apmixedsys_plls), + clk_data); + } return ret; } From 9475ebe129329817e5a1ea9e0c4f945bde993bb5 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Tue, 30 Jun 2026 12:30:50 +0200 Subject: [PATCH 02/45] clk: lan966x: Add MCHP_LAN966X_PCI dependency The lan966x clock controller depends on the LAN969x architecture or the LAN966x SoC. This clock controller can be used by the LAN966x PCI device and so it needs to be available when the LAN966x PCI device is enabled. Signed-off-by: Herve Codina Acked-by: Stephen Boyd Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 1717ce75a907..682840e67798 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -278,7 +278,7 @@ config COMMON_CLK_LAN966X tristate "Generic Clock Controller driver for LAN966X SoC" depends on HAS_IOMEM depends on OF - depends on SOC_LAN966 || ARCH_LAN969X || COMPILE_TEST + depends on SOC_LAN966 || ARCH_LAN969X || MCHP_LAN966X_PCI || COMPILE_TEST help This driver provides support for Generic Clock Controller(GCK) on LAN966X SoC. GCK generates and supplies clock to various peripherals From d1828369fafb01678761de5712e38dbf6cfa7e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20L=C3=B6bl?= Date: Wed, 1 Jul 2026 16:51:00 +0200 Subject: [PATCH 03/45] dt-bindings: clock: si544: add si549 compatible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding Si549 compatible, which uses different internal oscillator frequency. Like in Si544 case, there are three types ending with latters a,b and c. Each with different maximum output frequency. Also fix datasheet links after Silicon Labs acquisition by Skyworks Solutions. Signed-off-by: Pavel Löbl Acked-by: Conor Dooley Signed-off-by: Brian Masney --- .../devicetree/bindings/clock/silabs,si544.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/clock/silabs,si544.yaml b/Documentation/devicetree/bindings/clock/silabs,si544.yaml index f87e71867108..218f5abdef26 100644 --- a/Documentation/devicetree/bindings/clock/silabs,si544.yaml +++ b/Documentation/devicetree/bindings/clock/silabs,si544.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/clock/silabs,si544.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Silicon Labs SI514/SI544 clock generator +title: Silicon Labs SI514/SI544/SI549 clock generator maintainers: - Mike Looijmans @@ -13,8 +13,9 @@ description: > Silicon Labs 514/544 programmable I2C clock generator. Details about the device can be found in the datasheet: - https://www.silabs.com/Support%20Documents/TechnicalDocs/si514.pdf - https://www.silabs.com/documents/public/data-sheets/si544-datasheet.pdf + https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/data-sheets/Si514.pdf + https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/data-sheets/si544-datasheet.pdf + https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/data-sheets/si549-datasheet.pdf properties: compatible: @@ -23,6 +24,9 @@ properties: - silabs,si544a - silabs,si544b - silabs,si544c + - silabs,si549a + - silabs,si549b + - silabs,si549c reg: maxItems: 1 From bdba63b59897e4f79eafd09601fa76a14c680b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20L=C3=B6bl?= Date: Wed, 1 Jul 2026 16:51:01 +0200 Subject: [PATCH 04/45] clk: si544: add support for si549 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for Si549, which only differs from Si544 in internal oscilator frequency. Si549 also comes in three types A,B and C. Each having different maximum output frequency. This patch also fixes maximum frequency of Si544 C. Which should be 325MHz according to datasheet. Signed-off-by: Pavel Löbl Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Kconfig | 6 +-- drivers/clk/clk-si544.c | 82 ++++++++++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 682840e67798..b80c957b0040 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -155,12 +155,12 @@ config COMMON_CLK_SI514 generator. config COMMON_CLK_SI544 - tristate "Clock driver for SiLabs 544 devices" + tristate "Clock driver for SiLabs 544 and compatible devices" depends on I2C select REGMAP_I2C help - This driver supports the Silicon Labs 544 programmable clock - generator. + This driver supports the Silicon Labs 544/549 programmable clock + generators. config COMMON_CLK_SI570 tristate "Clock driver for SiLabs 570 and compatible devices" diff --git a/drivers/clk/clk-si544.c b/drivers/clk/clk-si544.c index 09c06ecec1a5..b13aeead9c02 100644 --- a/drivers/clk/clk-si544.c +++ b/drivers/clk/clk-si544.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Driver for Silicon Labs Si544 Programmable Oscillator + * Driver for Silicon Labs Si544/Si549 Programmable Oscillator * Copyright (C) 2018 Topic Embedded Products * Author: Mike Looijmans */ @@ -40,7 +40,9 @@ #define SI544_MIN_FREQ 200000U /* Si544 Internal oscillator runs at 55.05 MHz */ -#define FXO 55050000U +#define SI544_XO_FREQ 55050000U +/* Si549 Internal oscilator runs at 152.60 MHz */ +#define SI549_XO_FREQ 152600000U /* VCO range is 10.8 .. 12.1 GHz, max depends on speed grade */ #define FVCO_MIN 10800000000ULL @@ -56,11 +58,16 @@ #define DELTA_M_FRAC_NUM 19 #define DELTA_M_FRAC_DEN 20000 +struct si544_clk_desc { + unsigned long max_freq; + unsigned long xo_freq; +}; + struct clk_si544 { struct clk_hw hw; struct regmap *regmap; struct i2c_client *i2c_client; - unsigned long max_freq; + const struct si544_clk_desc *chip_info; }; #define to_clk_si544(_hw) container_of(_hw, struct clk_si544, hw) @@ -79,6 +86,7 @@ struct clk_si544_muldiv { u16 hs_div; u8 ls_div_bits; s32 delta_m; + u32 xo_freq; }; /* Enables or disables the output driver */ @@ -145,6 +153,8 @@ static int si544_get_muldiv(struct clk_si544 *data, settings->delta_m = reg[0] << 8 | reg[1] << 16 | reg[2] << 24; settings->delta_m >>= 8; + settings->xo_freq = data->chip_info->xo_freq; + return 0; } @@ -193,7 +203,7 @@ static bool is_valid_frequency(const struct clk_si544 *data, if (frequency < SI544_MIN_FREQ) return false; - return frequency <= data->max_freq; + return frequency <= data->chip_info->max_freq; } /* Calculate divider settings for a given frequency */ @@ -201,6 +211,7 @@ static int si544_calc_muldiv(struct clk_si544_muldiv *settings, unsigned long frequency) { u64 vco; + u32 fxo = settings->xo_freq; u32 ls_freq; u32 tmp; u8 res; @@ -238,13 +249,13 @@ static int si544_calc_muldiv(struct clk_si544_muldiv *settings, vco = (u64)ls_freq * settings->hs_div; /* Calculate the integer part of the feedback divider */ - tmp = do_div(vco, FXO); + tmp = do_div(vco, fxo); settings->fb_div_int = vco; /* And the fractional bits using the remainder */ vco = (u64)tmp << 32; - vco += FXO / 2; /* Round to nearest multiple */ - do_div(vco, FXO); + vco += fxo / 2; /* Round to nearest multiple */ + do_div(vco, fxo); settings->fb_div_frac = vco; /* Reset the frequency adjustment */ @@ -258,15 +269,16 @@ static unsigned long si544_calc_center_rate( const struct clk_si544_muldiv *settings) { u32 d = settings->hs_div * BIT(settings->ls_div_bits); + u32 fxo = settings->xo_freq; u64 vco; /* Calculate VCO from the fractional part */ - vco = (u64)settings->fb_div_frac * FXO; - vco += (FXO / 2); + vco = (u64)settings->fb_div_frac * fxo; + vco += (fxo / 2); vco >>= 32; /* Add the integer part of the VCO frequency */ - vco += (u64)settings->fb_div_int * FXO; + vco += (u64)settings->fb_div_int * fxo; /* Apply divider to obtain the generated frequency */ do_div(vco, d); @@ -446,7 +458,7 @@ static int si544_probe(struct i2c_client *client) init.num_parents = 0; data->hw.init = &init; data->i2c_client = client; - data->max_freq = (uintptr_t)i2c_get_match_data(client); + data->chip_info = i2c_get_match_data(client); if (of_property_read_string(client->dev.of_node, "clock-output-names", &init.name)) @@ -478,18 +490,54 @@ static int si544_probe(struct i2c_client *client) return 0; } +static const struct si544_clk_desc clk_si544a_info = { + .xo_freq = SI544_XO_FREQ, + .max_freq = 1500000000, +}; + +static const struct si544_clk_desc clk_si544b_info = { + .xo_freq = SI544_XO_FREQ, + .max_freq = 800000000, +}; + +static const struct si544_clk_desc clk_si544c_info = { + .xo_freq = SI544_XO_FREQ, + .max_freq = 325000000, +}; + +static const struct si544_clk_desc clk_si549a_info = { + .xo_freq = SI549_XO_FREQ, + .max_freq = 1500000000, +}; + +static const struct si544_clk_desc clk_si549b_info = { + .xo_freq = SI549_XO_FREQ, + .max_freq = 800000000, +}; + +static const struct si544_clk_desc clk_si549c_info = { + .xo_freq = SI549_XO_FREQ, + .max_freq = 325000000, +}; + static const struct i2c_device_id si544_id[] = { - { "si544a", 1500000000 }, - { "si544b", 800000000 }, - { "si544c", 350000000 }, + { "si544a", (kernel_ulong_t)&clk_si544a_info }, + { "si544b", (kernel_ulong_t)&clk_si544b_info }, + { "si544c", (kernel_ulong_t)&clk_si544c_info }, + { "si549a", (kernel_ulong_t)&clk_si549a_info }, + { "si549b", (kernel_ulong_t)&clk_si549b_info }, + { "si549c", (kernel_ulong_t)&clk_si549c_info }, { } }; MODULE_DEVICE_TABLE(i2c, si544_id); static const struct of_device_id clk_si544_of_match[] = { - { .compatible = "silabs,si544a", .data = (void *)1500000000 }, - { .compatible = "silabs,si544b", .data = (void *)800000000 }, - { .compatible = "silabs,si544c", .data = (void *)350000000 }, + { .compatible = "silabs,si544a", .data = &clk_si544a_info }, + { .compatible = "silabs,si544b", .data = &clk_si544b_info }, + { .compatible = "silabs,si544c", .data = &clk_si544c_info }, + { .compatible = "silabs,si549a", .data = &clk_si549a_info }, + { .compatible = "silabs,si549b", .data = &clk_si549b_info }, + { .compatible = "silabs,si549c", .data = &clk_si549c_info }, { } }; MODULE_DEVICE_TABLE(of, clk_si544_of_match); From 096ab64a0e69460bfb3557d09e17d2933280a1c6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:44:50 +0800 Subject: [PATCH 05/45] clk: mediatek: mt6735-vdecsys: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt6735-vdecsys.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/mediatek/clk-mt6735-vdecsys.c b/drivers/clk/mediatek/clk-mt6735-vdecsys.c index 8817085fc1db..d53ebf92e878 100644 --- a/drivers/clk/mediatek/clk-mt6735-vdecsys.c +++ b/drivers/clk/mediatek/clk-mt6735-vdecsys.c @@ -63,6 +63,7 @@ static const struct of_device_id of_match_mt6735_vdecsys[] = { { .compatible = "mediatek,mt6735-vdecsys", .data = &vdecsys_clks }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, of_match_mt6735_vdecsys); static struct platform_driver clk_mt6735_vdecsys = { .probe = mtk_clk_simple_probe, From 36cf8d279ff90835f4b002dec28eedb059554842 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 4 Jul 2026 20:48:08 +0800 Subject: [PATCH 06/45] clk: mediatek: mt6735-vencsys: add missing MODULE_DEVICE_TABLE() The driver has an OF match table wired to .of_match_table, but does not export the table with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE(of, ...) entry so module alias information is generated for OF based module autoloading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the platform driver, and the missing module alias publication. Signed-off-by: Pengpeng Hou Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt6735-vencsys.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/mediatek/clk-mt6735-vencsys.c b/drivers/clk/mediatek/clk-mt6735-vencsys.c index 8dec7f98492a..64ba9da9fd42 100644 --- a/drivers/clk/mediatek/clk-mt6735-vencsys.c +++ b/drivers/clk/mediatek/clk-mt6735-vencsys.c @@ -37,6 +37,7 @@ static const struct of_device_id of_match_mt6735_vencsys[] = { { .compatible = "mediatek,mt6735-vencsys", .data = &vencsys_clks }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, of_match_mt6735_vencsys); static struct platform_driver clk_mt6735_vencsys = { .probe = mtk_clk_simple_probe, From 23454c4635b9f01af303118cbffd8cb3b3b57b6b Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 5 Jul 2026 18:35:24 +0200 Subject: [PATCH 07/45] clk: mediatek: remove unneeded 'fast_io' parameter in regmap_config When using MMIO with regmap, fast_io is implied. No need to set it again. Signed-off-by: Wolfram Sang Reviewed-by: Brian Masney Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt8196-vlpckgen.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/clk/mediatek/clk-mt8196-vlpckgen.c b/drivers/clk/mediatek/clk-mt8196-vlpckgen.c index 7dcc164627c5..ee88dc4a231a 100644 --- a/drivers/clk/mediatek/clk-mt8196-vlpckgen.c +++ b/drivers/clk/mediatek/clk-mt8196-vlpckgen.c @@ -629,7 +629,6 @@ static const struct regmap_config vlpckgen_regmap_config = { .val_bits = 32, .reg_stride = 4, .max_register = 0x1000, - .fast_io = true, }; static int clk_mt8196_vlp_probe(struct platform_device *pdev) From 584dfca970a256fb4af0121017862a9259cc019e Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 8 Jul 2026 10:40:44 +0200 Subject: [PATCH 08/45] clk: en7523: reset PCIE HB on init for AN7581 It was reported that PCIE HB should be reset for AN7581 or some instability or link training issue will be present on warm boot scenario. Reset PCIE HB on clk HW init to handle warm boot problem with PCIe. Signed-off-by: Christian Marangi Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-en7523.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/clk/clk-en7523.c b/drivers/clk/clk-en7523.c index 1ab0e2eca5d3..c450d89bfa96 100644 --- a/drivers/clk/clk-en7523.c +++ b/drivers/clk/clk-en7523.c @@ -57,6 +57,7 @@ #define REG_RST_CTRL2 0x830 #define REG_RST_CTRL1 0x834 +#define REG_PCIE_HB_RST BIT(29) #define EN751221_REG_RST_DMT 0x84 #define EN751221_REG_RST_USB 0xec @@ -853,6 +854,12 @@ static int en7581_clk_hw_init(struct platform_device *pdev, val = readl(base + REG_NP_SCU_PCIC); writel(val | 3, base + REG_NP_SCU_PCIC); + val = readl(base + REG_RST_CTRL1); + val |= REG_PCIE_HB_RST; + writel(val, base + REG_RST_CTRL1); + val &= ~REG_PCIE_HB_RST; + writel(val, base + REG_RST_CTRL1); + return en7581_reset_register(&pdev->dev, base, en7581_rst_map, ARRAY_SIZE(en7581_rst_map), en7581_rst_ofs); From f726279f5eab813f9a8b6f38ddf2a4b062d038ff Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 19:20:58 +0800 Subject: [PATCH 09/45] clk: tegra: tegra124-emc: put EMC node on register failure tegra124_clk_register_emc() stores a device node reference returned by of_parse_phandle() in tegra->emc_node. If clk_register() fails, the function returns an error before that reference can be consumed and released by the normal runtime path. The tegra_clk_emc object is freed on this failure path, but freeing the object does not drop the OF node reference stored in it. Drop the EMC node reference before freeing the tegra_clk_emc object. of_node_put() is safe for a NULL node, so this also covers the case where the phandle is absent. Fixes: 2db04f16b589 ("clk: tegra: Add EMC clock driver") Signed-off-by: Guangshuo Li Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-tegra124-emc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/tegra/clk-tegra124-emc.c b/drivers/clk/tegra/clk-tegra124-emc.c index f3b2c96fdcfc..94ac24ea1e6a 100644 --- a/drivers/clk/tegra/clk-tegra124-emc.c +++ b/drivers/clk/tegra/clk-tegra124-emc.c @@ -537,6 +537,7 @@ struct clk *tegra124_clk_register_emc(void __iomem *base, struct device_node *np clk = clk_register(NULL, &tegra->hw); if (IS_ERR(clk)) { + of_node_put(tegra->emc_node); kfree(tegra); return clk; } From 540d91480bcb1b28a62d7023aa70947ea44c55b9 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Thu, 9 Jul 2026 14:23:00 +0200 Subject: [PATCH 10/45] clk: mediatek: pllfh: Fix IO remapping leak in register_pllfhs error path When mtk_clk_register_pllfhs function fails to register a PLL, it unregisters all PLLs and cleans up itself in its error path before returning, so the function callers don't need to do it. But contrary to mtk_clk_unregister_pllfhs function, that does almost the same sequence, it does not free the IO memory mapped on fhctl node, leading to a leak. Fix this leak by factorizing the cleanup sequence in a new private function and use it both mtk_clk_register_pllfhs and mtk_clk_unregister_pllfhs functions. Also, change the loop index start value to avoid the -1 operation on index at each loop. Fixes: d7964de8a8ea ("clk: mediatek: Add new clock driver to handle FHCTL hardware") Signed-off-by: Louis-Alexis Eyraud Reviewed-by: Brian Masney Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-pllfh.c | 98 ++++++++++++++++---------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/drivers/clk/mediatek/clk-pllfh.c b/drivers/clk/mediatek/clk-pllfh.c index aa95cd9197b3..6249fb87b1f5 100644 --- a/drivers/clk/mediatek/clk-pllfh.c +++ b/drivers/clk/mediatek/clk-pllfh.c @@ -197,12 +197,56 @@ static void mtk_clk_unregister_pllfh(struct clk_hw *hw) kfree(fh); } +static void mtk_clk_cleanup_pllfhs(void __iomem *iomem_base, + const struct mtk_pll_data *plls, int num_plls, + void __iomem *iomem_fhctl_base, + struct mtk_pllfh_data *pllfhs, int num_fhs, + struct clk_hw_onecell_data *clk_data) +{ + void __iomem *base = iomem_base; + void __iomem *fhctl_base = iomem_fhctl_base; + int i; + + for (i = num_plls - 1; i >= 0; i--) { + const struct mtk_pll_data *pll = &plls[i]; + struct mtk_pllfh_data *pllfh; + bool use_fhctl; + + if (IS_ERR_OR_NULL(clk_data->hws[pll->id])) + continue; + + pllfh = get_pllfh_by_id(pllfhs, num_fhs, pll->id); + use_fhctl = fhctl_is_supported_and_enabled(pllfh); + + if (!base) + base = mtk_clk_pll_get_base(clk_data->hws[pll->id], + pll); + + if (use_fhctl) { + if (!fhctl_base) + fhctl_base = pllfh->state.base; + mtk_clk_unregister_pllfh(clk_data->hws[pll->id]); + } else { + mtk_clk_unregister_pll(clk_data->hws[pll->id]); + } + + clk_data->hws[pll->id] = ERR_PTR(-ENOENT); + } + + if (fhctl_base) + iounmap(fhctl_base); + + if (base) + iounmap(base); +} + + int mtk_clk_register_pllfhs(struct device *dev, const struct mtk_pll_data *plls, int num_plls, struct mtk_pllfh_data *pllfhs, int num_fhs, struct clk_hw_onecell_data *clk_data) { - void __iomem *base; + void __iomem *base, *fhctl_base = NULL; int i; struct clk_hw *hw; @@ -238,24 +282,8 @@ int mtk_clk_register_pllfhs(struct device *dev, return 0; err: - while (--i >= 0) { - const struct mtk_pll_data *pll = &plls[i]; - struct mtk_pllfh_data *pllfh; - bool use_fhctl; - - pllfh = get_pllfh_by_id(pllfhs, num_fhs, pll->id); - use_fhctl = fhctl_is_supported_and_enabled(pllfh); - - if (use_fhctl) - mtk_clk_unregister_pllfh(clk_data->hws[pll->id]); - else - mtk_clk_unregister_pll(clk_data->hws[pll->id]); - - clk_data->hws[pll->id] = ERR_PTR(-ENOENT); - } - - iounmap(base); - + mtk_clk_cleanup_pllfhs(base, plls, i, fhctl_base, pllfhs, num_fhs, + clk_data); return PTR_ERR(hw); } EXPORT_SYMBOL_GPL(mtk_clk_register_pllfhs); @@ -264,38 +292,10 @@ void mtk_clk_unregister_pllfhs(const struct mtk_pll_data *plls, int num_plls, struct mtk_pllfh_data *pllfhs, int num_fhs, struct clk_hw_onecell_data *clk_data) { - void __iomem *base = NULL, *fhctl_base = NULL; - int i; - if (!clk_data) return; - for (i = num_plls; i > 0; i--) { - const struct mtk_pll_data *pll = &plls[i - 1]; - struct mtk_pllfh_data *pllfh; - bool use_fhctl; - - if (IS_ERR_OR_NULL(clk_data->hws[pll->id])) - continue; - - pllfh = get_pllfh_by_id(pllfhs, num_fhs, pll->id); - use_fhctl = fhctl_is_supported_and_enabled(pllfh); - - if (use_fhctl) { - fhctl_base = pllfh->state.base; - mtk_clk_unregister_pllfh(clk_data->hws[pll->id]); - } else { - base = mtk_clk_pll_get_base(clk_data->hws[pll->id], - pll); - mtk_clk_unregister_pll(clk_data->hws[pll->id]); - } - - clk_data->hws[pll->id] = ERR_PTR(-ENOENT); - } - - if (fhctl_base) - iounmap(fhctl_base); - - iounmap(base); + mtk_clk_cleanup_pllfhs(NULL, plls, num_plls, NULL, pllfhs, + num_fhs, clk_data); } EXPORT_SYMBOL_GPL(mtk_clk_unregister_pllfhs); From 011d8de504bc84402aabc1dda1cf0552fe9a5af2 Mon Sep 17 00:00:00 2001 From: Kostas Damaskinakis Date: Fri, 10 Jul 2026 17:15:20 +0300 Subject: [PATCH 11/45] clk: eswin: Zero-initialize stack-allocated clk_init_data eswin_clk_register_pll() and eswin_register_clkdiv() declare a struct clk_init_data on the stack and only initialize some of its fields (parent_data respectively parent_hws). clk_core_populate_parent_map() checks parent_names first and parent_data second before falling back to parent_hws, so leftover stack garbage in the uninitialized fields hijacks parent resolution and the clk core dereferences a bogus pointer: Unable to handle kernel NULL pointer dereference at virtual address 000000000000000c Oops [#1] epc : __clk_register+0x31a/0x7f0 [] __clk_register+0x31a/0x7f0 [] devm_clk_hw_register+0x2a/0x94 [] eswin_register_clkdiv+0x80/0xd0 [] eswin_clk_register_clks+0x162/0x1a0 [] eic7700_clk_probe+0x146/0x180 [] platform_probe+0x3c/0x7a Observed on EIC7700 hardware (with the driver backported to a 6.17 tree); whether the bug triggers depends entirely on what the stack happens to contain when the registration helpers run. Zero-initialize both structures. Fixes: cd44f127c1d4 ("clk: eswin: Add eic7700 clock driver") Signed-off-by: Kostas Damaskinakis Reviewed-by: Brian Masney Acked-by: Xuyang Dong Signed-off-by: Brian Masney --- drivers/clk/eswin/clk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/eswin/clk.c b/drivers/clk/eswin/clk.c index e09a52cc3587..79d1e4c5e637 100644 --- a/drivers/clk/eswin/clk.c +++ b/drivers/clk/eswin/clk.c @@ -204,7 +204,7 @@ int eswin_clk_register_pll(struct device *dev, struct eswin_pll_clock *clks, int nums, struct eswin_clock_data *data) { struct eswin_clk_pll *p_clk = NULL; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_hw *clk_hw; int i, ret; @@ -419,7 +419,7 @@ struct clk_hw *eswin_register_clkdiv(struct device *dev, unsigned int id, unsigned long priv_flag, spinlock_t *lock) { struct eswin_divider_clock *dclk; - struct clk_init_data init; + struct clk_init_data init = {}; struct clk_hw *clk_hw; int ret; From ccda84fcbf3a972973f772384935928f41817b3a Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 11 Jul 2026 23:17:42 +0900 Subject: [PATCH 12/45] clk: palmas: Manage external-control prepare with devm palmas_clks_init_configure() prepares the clock when an external control pin is configured. The current driver only drops that prepare reference when external control configuration fails. If provider registration fails after that point, or if the driver is later removed, the prepare reference remains held. Register a device-managed action after clk_prepare() succeeds. This balances the prepare reference on subsequent probe failure and driver removal. Fixes: 942d1d674931 ("clk: Add driver for Palmas clk32kg and clk32kgaudio clocks") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-palmas.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/clk/clk-palmas.c b/drivers/clk/clk-palmas.c index 39049f62dbbb..86a51edac827 100644 --- a/drivers/clk/clk-palmas.c +++ b/drivers/clk/clk-palmas.c @@ -194,6 +194,13 @@ static void palmas_clks_get_clk_data(struct platform_device *pdev, cinfo->ext_control_pin = prop; } +static void palmas_clks_unprepare_ext_control(void *data) +{ + struct palmas_clock_info *cinfo = data; + + clk_unprepare(cinfo->hw.clk); +} + static int palmas_clks_init_configure(struct palmas_clock_info *cinfo) { int ret; @@ -214,13 +221,18 @@ static int palmas_clks_init_configure(struct palmas_clock_info *cinfo) return ret; } + ret = devm_add_action_or_reset(cinfo->dev, + palmas_clks_unprepare_ext_control, + cinfo); + if (ret) + return ret; + ret = palmas_ext_control_req_config(cinfo->palmas, cinfo->clk_desc->sleep_reqstr_id, cinfo->ext_control_pin, true); if (ret < 0) { dev_err(cinfo->dev, "Ext config for %s failed, %d\n", cinfo->clk_desc->clk_name, ret); - clk_unprepare(cinfo->hw.clk); return ret; } } From 10f9dc786a1b53b81073b1b155ef4815e3b7b6f7 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sat, 11 Jul 2026 23:17:43 +0900 Subject: [PATCH 13/45] clk: palmas: Use devm_of_clk_add_hw_provider() Use devm_of_clk_add_hw_provider() to register the clock provider so its lifetime is tied to the device. This removes the need for a remove callback that only unregisters the provider. Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-palmas.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/clk/clk-palmas.c b/drivers/clk/clk-palmas.c index 86a51edac827..fdbf8445ebc2 100644 --- a/drivers/clk/clk-palmas.c +++ b/drivers/clk/clk-palmas.c @@ -242,7 +242,6 @@ static int palmas_clks_init_configure(struct palmas_clock_info *cinfo) static int palmas_clks_probe(struct platform_device *pdev) { struct palmas *palmas = dev_get_drvdata(pdev->dev.parent); - struct device_node *node = pdev->dev.of_node; const struct palmas_clks_of_match_data *match_data; struct palmas_clock_info *cinfo; int ret; @@ -276,24 +275,19 @@ static int palmas_clks_probe(struct platform_device *pdev) return ret; } - ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &cinfo->hw); + ret = devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, + &cinfo->hw); if (ret < 0) dev_err(&pdev->dev, "Fail to add clock driver, %d\n", ret); return ret; } -static void palmas_clks_remove(struct platform_device *pdev) -{ - of_clk_del_provider(pdev->dev.of_node); -} - static struct platform_driver palmas_clks_driver = { .driver = { .name = "palmas-clk", .of_match_table = palmas_clks_of_match, }, .probe = palmas_clks_probe, - .remove = palmas_clks_remove, }; module_platform_driver(palmas_clks_driver); From 0cad34fd953082a1bef863a0e446dc2559b8eed0 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 14 Jul 2026 13:25:25 +0800 Subject: [PATCH 14/45] clk: mmp: pxa1908-apbc: Remove unneeded semicolon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Signed-off-by: Chen Ni Reviewed-by: Brian Masney Acked-by: Duje Mihanović Signed-off-by: Brian Masney --- drivers/clk/mmp/clk-pxa1908-apbc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/mmp/clk-pxa1908-apbc.c b/drivers/clk/mmp/clk-pxa1908-apbc.c index 438ece4f047d..16c1f13bca1f 100644 --- a/drivers/clk/mmp/clk-pxa1908-apbc.c +++ b/drivers/clk/mmp/clk-pxa1908-apbc.c @@ -114,7 +114,7 @@ static void pxa1908_clk_reset_init(struct device_node *np, cells[i].bits = BIT(2); cells[i].flags = 0; cells[i].lock = apbc_gate_clks[i].lock; - }; + } mmp_clk_reset_register(np, cells, nr_cells); } From 2a97ca25f7d5fc7c7a475ff2a92b3fd2cfc8faf1 Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 14 Jul 2026 13:30:59 +0800 Subject: [PATCH 15/45] clk: mmp: pxa1908-apbcp: Remove unneeded semicolon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary semicolons reported by Coccinelle/coccicheck and the semantic patch at scripts/coccinelle/misc/semicolon.cocci. Signed-off-by: Chen Ni Reviewed-by: Brian Masney Acked-by: Duje Mihanović Signed-off-by: Brian Masney --- drivers/clk/mmp/clk-pxa1908-apbcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/mmp/clk-pxa1908-apbcp.c b/drivers/clk/mmp/clk-pxa1908-apbcp.c index 1aa476103553..4e646e8b7440 100644 --- a/drivers/clk/mmp/clk-pxa1908-apbcp.c +++ b/drivers/clk/mmp/clk-pxa1908-apbcp.c @@ -61,7 +61,7 @@ static void pxa1908_clk_reset_init(struct device_node *np, cells[i].bits = BIT(2); cells[i].flags = 0; cells[i].lock = apbcp_gate_clks[i].lock; - }; + } mmp_clk_reset_register(np, cells, nr_cells); } From 18e9d14cbac33db1c1fb933c26a736eef53dd538 Mon Sep 17 00:00:00 2001 From: longlong yan Date: Tue, 14 Jul 2026 14:51:32 +0800 Subject: [PATCH 16/45] clk/x86: pmc_atom: add kasprintf return value check The kasprintf() function returns NULL on memory allocation failure, but the code in plt_clk_register() was not checking this return value. If kasprintf fails, init.name would be NULL and could cause NULL pointer dereference when clkdev_hw_create() uses it. Add proper error checking for the kasprintf() return value and return ERR_PTR(-ENOMEM) on failure. Fixes: 1141d9d08184 ("clk: x86: Add Atom PMC platform clocks") Signed-off-by: longlong yan Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/x86/clk-pmc-atom.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/x86/clk-pmc-atom.c b/drivers/clk/x86/clk-pmc-atom.c index 99291ba65da7..08c83e0abc41 100644 --- a/drivers/clk/x86/clk-pmc-atom.c +++ b/drivers/clk/x86/clk-pmc-atom.c @@ -160,6 +160,9 @@ static struct clk_plt *plt_clk_register(struct platform_device *pdev, int id, return ERR_PTR(-ENOMEM); init.name = kasprintf(GFP_KERNEL, "%s_%d", PLT_CLK_NAME_BASE, id); + if (!init.name) + return ERR_PTR(-ENOMEM); + init.ops = &plt_clk_ops; init.flags = 0; init.parent_names = parent_names; From d3045184699d6220e5c03c2d6b1dfc449c3b873e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 14 Jul 2026 18:50:18 +0300 Subject: [PATCH 17/45] clk: document that clk_get_parent() returns NULL The documentation in the clk.h file says that clk_get_parent() returns error pointers but it doesn't. It's also not consistent with the comments next to the clk_get_parent() implementation which say that it returns NULL when the clk is NULL. Update the comments so they are consistent and accurate and say that it returns NULL. Signed-off-by: Dan Carpenter Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- include/linux/clk.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/clk.h b/include/linux/clk.h index 998ba3f261da..993fd6e916c7 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -951,8 +951,8 @@ int clk_set_parent(struct clk *clk, struct clk *parent); * clk_get_parent - get the parent clock source for this clock * @clk: clock source * - * Returns struct clk corresponding to parent clock source, or - * valid IS_ERR() condition containing errno. + * Returns struct clk corresponding to parent clock source, or NULL + * if clk is NULL. */ struct clk *clk_get_parent(struct clk *clk); From 438623bb9350609069ac9fdb3d87b64beb3e9407 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 19 Jul 2026 14:50:39 -0700 Subject: [PATCH 18/45] clk: stm32: add missing bitfield.h header It seems some ARM header includes this and the build passes there, but nowhere else. Note that the driver has COMPILE_TEST in depends. Fixes: 37ae8501cdb0 ("clk: stm32: introduce clocks for STM32MP21 platfor") Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/stm32/clk-stm32mp21.c | 1 + drivers/clk/stm32/clk-stm32mp25.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/clk/stm32/clk-stm32mp21.c b/drivers/clk/stm32/clk-stm32mp21.c index c8a37b716bd5..bdb17419908c 100644 --- a/drivers/clk/stm32/clk-stm32mp21.c +++ b/drivers/clk/stm32/clk-stm32mp21.c @@ -4,6 +4,7 @@ * Author: Gabriel Fernandez for STMicroelectronics. */ +#include #include #include #include diff --git a/drivers/clk/stm32/clk-stm32mp25.c b/drivers/clk/stm32/clk-stm32mp25.c index 52f0e8a12926..eb0bc918ecee 100644 --- a/drivers/clk/stm32/clk-stm32mp25.c +++ b/drivers/clk/stm32/clk-stm32mp25.c @@ -4,6 +4,7 @@ * Author: Gabriel Fernandez for STMicroelectronics. */ +#include #include #include #include From 745dfe874d5b9136090af69eba3f61170ceaa168 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 19 Jul 2026 14:50:40 -0700 Subject: [PATCH 19/45] clk: stm32: allow STM32MP COMPILE_TEST builds COMMON_CLK_STM32MP already allows COMPILE_TEST, but the parent clock Makefile only descends into drivers/clk/stm32 for ARCH_STM32. Use the STM32MP clock symbol for that directory gate instead. Tested with: make LLVM=1 ARCH=loongarch drivers/clk/stm32/ Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index cc108a75a900..7c928f962d0b 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -151,7 +151,7 @@ obj-y += spacemit/ obj-$(CONFIG_PLAT_SPEAR) += spear/ obj-y += sprd/ obj-$(CONFIG_ARCH_STI) += st/ -obj-$(CONFIG_ARCH_STM32) += stm32/ +obj-$(CONFIG_COMMON_CLK_STM32MP) += stm32/ obj-y += starfive/ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-y += sunxi-ng/ From 3194c0f961a4d198e28d055b0be3f597b85012c9 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 19 Jul 2026 14:54:56 -0700 Subject: [PATCH 20/45] clk: hisilicon: hi3660-stub: use devm_platform_ioremap_resource Replace the open-coded platform_get_resource() plus devm_ioremap() sequence with devm_platform_ioremap_resource(), which fetches the resource, requests the region and maps it in one call. Switch the error check to IS_ERR()/PTR_ERR() and drop the now-unused struct resource pointer. The only in-tree user (stub_clock@e896b500 in hi3660.dtsi) has a single 0x100-byte reg region, so the region reservation added by devm_ioremap_resource() is exclusive and does not introduce overlap failures. Built for ARM (drivers/clk/hisilicon/clk-hi3660-stub.o) with LLVM=1. Assisted-by: opencode:hy3-free Signed-off-by: Rosen Penev Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/hisilicon/clk-hi3660-stub.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/clk/hisilicon/clk-hi3660-stub.c b/drivers/clk/hisilicon/clk-hi3660-stub.c index 7c8b00ee6019..1b81f26670ed 100644 --- a/drivers/clk/hisilicon/clk-hi3660-stub.c +++ b/drivers/clk/hisilicon/clk-hi3660-stub.c @@ -124,7 +124,6 @@ static struct clk_hw *hi3660_stub_clk_hw_get(struct of_phandle_args *clkspec, static int hi3660_stub_clk_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; unsigned int i; int ret; @@ -139,12 +138,9 @@ static int hi3660_stub_clk_probe(struct platform_device *pdev) if (IS_ERR(stub_clk_chan.mbox)) return PTR_ERR(stub_clk_chan.mbox); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EINVAL; - freq_reg = devm_ioremap(dev, res->start, resource_size(res)); - if (!freq_reg) - return -ENOMEM; + freq_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(freq_reg)) + return PTR_ERR(freq_reg); freq_reg += HI3660_STUB_CLOCK_DATA; From f600fbdb2093b18f2e21401ba7c6325e4d2b5b16 Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 20 Jul 2026 08:38:04 +0800 Subject: [PATCH 21/45] clk: composite: Export devm composite pdata helper Allow modular clock drivers to use devm_clk_hw_register_composite_pdata() by exporting the helper. The non-devm composite helpers are already available to modules. Export this devm variant as well so users do not need to open-code devres cleanup. Signed-off-by: Jia Wang Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-composite.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c index 835b1e4e5869..11842bce5918 100644 --- a/drivers/clk/clk-composite.c +++ b/drivers/clk/clk-composite.c @@ -462,3 +462,4 @@ struct clk_hw *devm_clk_hw_register_composite_pdata(struct device *dev, rate_hw, rate_ops, gate_hw, gate_ops, flags); } +EXPORT_SYMBOL_GPL(devm_clk_hw_register_composite_pdata); From e1d57ac7fe53dc52bb8aab9db59008c7fec8d863 Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 20 Jul 2026 08:38:05 +0800 Subject: [PATCH 22/45] dt-bindings: clock: ultrarisc: Add DP1000 Clock Controller Add doc for the clock controller on the UltraRISC DP1000 RISC-V SoC. Signed-off-by: Jia Wang Reviewed-by: Conor Dooley Signed-off-by: Brian Masney --- .../bindings/clock/ultrarisc,dp1000-clk.yaml | 60 +++++++++++++++++++ MAINTAINERS | 7 +++ .../dt-bindings/clock/ultrarisc,dp1000-clk.h | 27 +++++++++ 3 files changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml create mode 100644 include/dt-bindings/clock/ultrarisc,dp1000-clk.h diff --git a/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml new file mode 100644 index 000000000000..ede565ec440c --- /dev/null +++ b/Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/ultrarisc,dp1000-clk.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: UltraRISC DP1000 Clock Controller + +maintainers: + - Jia Wang + +description: | + The UltraRISC DP1000 clock controller is driven from a single external + oscillator input. It provides a system PLL with fractional multiplier + and post-divider stages, several fixed-ratio derived clocks for + the on-chip subsystem, Clock Configuration Register (CCR) divider + outputs for GMAC and the UART, I2C, and SPI root clocks, and + per-instance gate clocks for UART0-3, I2C0-3, and SPI0-1. + + All available clocks are defined as preprocessor macros in + include/dt-bindings/clock/ultrarisc,dp1000-clk.h + +properties: + compatible: + const: ultrarisc,dp1000-clk + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + description: + External oscillator input clock used as the parent of the PLLs. + + "#clock-cells": + const: 1 + +required: + - compatible + - reg + - clocks + - "#clock-cells" + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller@11080000 { + compatible = "ultrarisc,dp1000-clk"; + reg = <0x0 0x11080000 0x0 0x1000>; + clocks = <&osc>; + #clock-cells = <1>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..ce93b28497e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27669,6 +27669,13 @@ S: Maintained F: drivers/usb/common/ulpi.c F: include/linux/ulpi/ +ULTRARISC DP1000 CLOCK DRIVER +M: Jia Wang +L: linux-clk@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml +F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h + ULTRARISC DP1000 PINCTRL DRIVER M: Jia Wang L: linux-gpio@vger.kernel.org diff --git a/include/dt-bindings/clock/ultrarisc,dp1000-clk.h b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h new file mode 100644 index 000000000000..751125f99965 --- /dev/null +++ b/include/dt-bindings/clock/ultrarisc,dp1000-clk.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +#ifndef _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H +#define _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H + +#define DP1000_CLK_SYSPLL 0 +#define DP1000_CLK_SYSPLL_DIV2 1 +#define DP1000_CLK_SUBSYS 2 +#define DP1000_CLK_GMAC 3 +#define DP1000_CLK_UART_ROOT 4 +#define DP1000_CLK_I2C_ROOT 5 +#define DP1000_CLK_SPI_ROOT 6 +#define DP1000_CLK_PCIE_DBI 7 +#define DP1000_CLK_PCIEX4_CORE 8 +#define DP1000_CLK_PCIEX16_CORE 9 +#define DP1000_CLK_PCIE_AUX 10 +#define DP1000_CLK_UART0 11 +#define DP1000_CLK_UART1 12 +#define DP1000_CLK_UART2 13 +#define DP1000_CLK_UART3 14 +#define DP1000_CLK_I2C0 15 +#define DP1000_CLK_I2C1 16 +#define DP1000_CLK_I2C2 17 +#define DP1000_CLK_I2C3 18 +#define DP1000_CLK_SPI0 19 +#define DP1000_CLK_SPI1 20 + +#endif /* _DT_BINDINGS_CLOCK_ULTRARISC_DP1000_CLK_H */ From 3948a36be42b67f43f1d3f113dad4c8b347efb9b Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 20 Jul 2026 08:38:06 +0800 Subject: [PATCH 23/45] clk: ultrarisc: Add DP1000 clock driver Add a clock driver for the UltraRISC DP1000 SoC. The clock tree is driven by a SYSPLL and provides fixed-factor clocks for the subsystem and PCIe, divider-based root clocks for GMAC and the UART, I2C, and SPI blocks, and per-instance gate clocks for UART0-3, I2C0-3, and SPI0-1. Signed-off-by: Jia Wang Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- MAINTAINERS | 1 + drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/ultrarisc/Kconfig | 18 ++ drivers/clk/ultrarisc/Makefile | 4 + drivers/clk/ultrarisc/clk-dp1000.c | 154 ++++++++++ drivers/clk/ultrarisc/clk-ultrarisc.c | 399 ++++++++++++++++++++++++++ drivers/clk/ultrarisc/clk-ultrarisc.h | 71 +++++ 8 files changed, 649 insertions(+) create mode 100644 drivers/clk/ultrarisc/Kconfig create mode 100644 drivers/clk/ultrarisc/Makefile create mode 100644 drivers/clk/ultrarisc/clk-dp1000.c create mode 100644 drivers/clk/ultrarisc/clk-ultrarisc.c create mode 100644 drivers/clk/ultrarisc/clk-ultrarisc.h diff --git a/MAINTAINERS b/MAINTAINERS index ce93b28497e6..4d21a2dca3fc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27674,6 +27674,7 @@ M: Jia Wang L: linux-clk@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/clock/ultrarisc,dp1000-clk.yaml +F: drivers/clk/ultrarisc/* F: include/dt-bindings/clock/ultrarisc,dp1000-clk.h ULTRARISC DP1000 PINCTRL DRIVER diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index b80c957b0040..f83dda6ffc88 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -541,6 +541,7 @@ source "drivers/clk/tenstorrent/Kconfig" source "drivers/clk/thead/Kconfig" source "drivers/clk/stm32/Kconfig" source "drivers/clk/ti/Kconfig" +source "drivers/clk/ultrarisc/Kconfig" source "drivers/clk/uniphier/Kconfig" source "drivers/clk/visconti/Kconfig" source "drivers/clk/x86/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 7c928f962d0b..dc56d0249450 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -159,6 +159,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += tenstorrent/ obj-$(CONFIG_ARCH_THEAD) += thead/ obj-y += ti/ +obj-y += ultrarisc/ obj-$(CONFIG_CLK_UNIPHIER) += uniphier/ obj-$(CONFIG_ARCH_U8500) += ux500/ obj-y += versatile/ diff --git a/drivers/clk/ultrarisc/Kconfig b/drivers/clk/ultrarisc/Kconfig new file mode 100644 index 000000000000..2eecc6ac3119 --- /dev/null +++ b/drivers/clk/ultrarisc/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config CLK_ULTRARISC + tristate + depends on OF + depends on ARCH_ULTRARISC || COMPILE_TEST + +config CLK_ULTRARISC_DP1000 + tristate "UltraRISC DP1000 clock controller" + select CLK_ULTRARISC + depends on OF && HAS_IOMEM + depends on ARCH_ULTRARISC || COMPILE_TEST + default ARCH_ULTRARISC + help + This driver provides the clock controller for the UltraRISC + DP1000 SoC. It exposes the PLL output, derived fixed-factor + clocks, programmable divider clocks, and peripheral gate + clocks to Linux consumers. diff --git a/drivers/clk/ultrarisc/Makefile b/drivers/clk/ultrarisc/Makefile new file mode 100644 index 000000000000..b013708c9444 --- /dev/null +++ b/drivers/clk/ultrarisc/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_CLK_ULTRARISC) += clk-ultrarisc.o +obj-$(CONFIG_CLK_ULTRARISC_DP1000) += clk-dp1000.o diff --git a/drivers/clk/ultrarisc/clk-dp1000.c b/drivers/clk/ultrarisc/clk-dp1000.c new file mode 100644 index 000000000000..ffea77c4cca0 --- /dev/null +++ b/drivers/clk/ultrarisc/clk-dp1000.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + */ + +#include + +#include + +#include "clk-ultrarisc.h" + +#define DP1000_PLL_CFG1_OFFSET 0x400 +#define DP1000_PLL_CFG2_OFFSET 0x404 + +#define DP1000_CCR_UART_OFFSET 0x220 +#define DP1000_CCR_I2C_OFFSET 0x224 +#define DP1000_CCR_GMAC_OFFSET 0x228 +#define DP1000_CCR_SPI_OFFSET 0x22c +#define DP1000_PERI_CLKENA_OFFSET 0x270 + +#define DP1000_CCR_LOAD BIT(16) + +#define DP1000_PERI_MAX_RATE 62500000UL +#define DP1000_CLK_NUM 21 + +static const struct ultrarisc_pll_layout dp1000_pll_layout = { + .cfg1_offset = DP1000_PLL_CFG1_OFFSET, + .cfg2_offset = DP1000_PLL_CFG2_OFFSET, + .frac_mask = GENMASK(23, 0), + .m_mask = GENMASK(23, 16), + .n_mask = GENMASK(11, 6), + .oddiv1_mask = GENMASK(1, 0), + .oddiv2_mask = GENMASK(4, 3), +}; + +static const struct ultrarisc_pll_desc dp1000_plls[] = { + { + .id = DP1000_CLK_SYSPLL, + .name = "syspll_clk", + }, +}; + +#define DP1000_FIXED_FACTOR(_id, _name, _parent, _mult, _div) \ + { \ + .id = (_id), \ + .name = (_name), \ + .parent_id = (_parent), \ + .mult = (_mult), \ + .div = (_div), \ + } + +#define DP1000_DIV(_id, _name, _offset, _parent, _max_rate) \ + { \ + .id = (_id), \ + .name = (_name), \ + .offset = (_offset), \ + .parent_id = (_parent), \ + .max_rate = (_max_rate), \ + .load_mask = DP1000_CCR_LOAD, \ + .div_shift = 8, \ + .div_width = 4, \ + .gate_bit = 0, \ + .divider_flags = CLK_DIVIDER_ONE_BASED, \ + .gate_flags = 0, \ + } + +#define DP1000_GATE(_id, _name, _parent, _bit) \ + { \ + .id = (_id), \ + .name = (_name), \ + .offset = DP1000_PERI_CLKENA_OFFSET, \ + .parent_id = (_parent), \ + .gate_bit = (_bit), \ + .gate_flags = 0, \ + } + +static const struct ultrarisc_fixed_factor_desc dp1000_fixed_factor_clks[] = { + DP1000_FIXED_FACTOR(DP1000_CLK_SYSPLL_DIV2, "syspll_div2_clk", + DP1000_CLK_SYSPLL, 1, 2), + DP1000_FIXED_FACTOR(DP1000_CLK_SUBSYS, "subsys_clk", + DP1000_CLK_SYSPLL_DIV2, 1, 2), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_DBI, "pcie_dbi_clk", + DP1000_CLK_SYSPLL, 1, 10), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX4_CORE, "pciex4_core_clk", + DP1000_CLK_SYSPLL, 1, 2), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIEX16_CORE, "pciex16_core_clk", + DP1000_CLK_SYSPLL, 1, 1), + DP1000_FIXED_FACTOR(DP1000_CLK_PCIE_AUX, "pcie_aux_clk", + DP1000_CLK_SYSPLL, 1, 40), +}; + +static const struct ultrarisc_divider_desc dp1000_divider_clks[] = { + DP1000_DIV(DP1000_CLK_GMAC, "gmac_clk", DP1000_CCR_GMAC_OFFSET, + DP1000_CLK_SYSPLL_DIV2, 0), + DP1000_DIV(DP1000_CLK_UART_ROOT, "uart_root_clk", + DP1000_CCR_UART_OFFSET, DP1000_CLK_SUBSYS, + DP1000_PERI_MAX_RATE), + DP1000_DIV(DP1000_CLK_I2C_ROOT, "i2c_root_clk", + DP1000_CCR_I2C_OFFSET, DP1000_CLK_SUBSYS, + DP1000_PERI_MAX_RATE), + DP1000_DIV(DP1000_CLK_SPI_ROOT, "spi_root_clk", + DP1000_CCR_SPI_OFFSET, DP1000_CLK_SUBSYS, + DP1000_PERI_MAX_RATE), +}; + +static const struct ultrarisc_gate_desc dp1000_gate_clks[] = { + DP1000_GATE(DP1000_CLK_UART0, "uart0_clk", DP1000_CLK_UART_ROOT, 0), + DP1000_GATE(DP1000_CLK_UART1, "uart1_clk", DP1000_CLK_UART_ROOT, 1), + DP1000_GATE(DP1000_CLK_UART2, "uart2_clk", DP1000_CLK_UART_ROOT, 2), + DP1000_GATE(DP1000_CLK_UART3, "uart3_clk", DP1000_CLK_UART_ROOT, 3), + DP1000_GATE(DP1000_CLK_I2C0, "i2c0_clk", DP1000_CLK_I2C_ROOT, 4), + DP1000_GATE(DP1000_CLK_I2C1, "i2c1_clk", DP1000_CLK_I2C_ROOT, 5), + DP1000_GATE(DP1000_CLK_I2C2, "i2c2_clk", DP1000_CLK_I2C_ROOT, 6), + DP1000_GATE(DP1000_CLK_I2C3, "i2c3_clk", DP1000_CLK_I2C_ROOT, 7), + DP1000_GATE(DP1000_CLK_SPI0, "spi0_clk", DP1000_CLK_SPI_ROOT, 8), + DP1000_GATE(DP1000_CLK_SPI1, "spi1_clk", DP1000_CLK_SPI_ROOT, 9), +}; + +static const struct ultrarisc_clk_soc_data dp1000_clk_soc_data = { + .num_clks = DP1000_CLK_NUM, + .pll_layout = &dp1000_pll_layout, + .plls = dp1000_plls, + .num_plls = ARRAY_SIZE(dp1000_plls), + .fixed_factors = dp1000_fixed_factor_clks, + .num_fixed_factors = ARRAY_SIZE(dp1000_fixed_factor_clks), + .dividers = dp1000_divider_clks, + .num_dividers = ARRAY_SIZE(dp1000_divider_clks), + .gates = dp1000_gate_clks, + .num_gates = ARRAY_SIZE(dp1000_gate_clks), +}; + +static int dp1000_clk_probe(struct platform_device *pdev) +{ + return ultrarisc_clk_probe(pdev, &dp1000_clk_soc_data); +} + +static const struct of_device_id dp1000_clk_of_match[] = { + { .compatible = "ultrarisc,dp1000-clk" }, + { } +}; +MODULE_DEVICE_TABLE(of, dp1000_clk_of_match); + +static struct platform_driver dp1000_clk_driver = { + .probe = dp1000_clk_probe, + .driver = { + .name = "ultrarisc-dp1000-clk", + .of_match_table = dp1000_clk_of_match, + }, +}; +module_platform_driver(dp1000_clk_driver); + +MODULE_IMPORT_NS("CLK_ULTRARISC"); +MODULE_DESCRIPTION("UltraRISC DP1000 clock controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.c b/drivers/clk/ultrarisc/clk-ultrarisc.c new file mode 100644 index 000000000000..bbcd2234ea71 --- /dev/null +++ b/drivers/clk/ultrarisc/clk-ultrarisc.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include "clk-ultrarisc.h" + +struct ultrarisc_pll_clk { + struct clk_hw hw; + void __iomem *base; + const struct ultrarisc_pll_layout *layout; +}; + +struct ultrarisc_divider_clk { + struct clk_divider divider; + struct clk_gate gate; + u32 load_mask; +}; + +#define to_ultrarisc_pll_clk(_hw) \ + container_of(_hw, struct ultrarisc_pll_clk, hw) + +static inline struct ultrarisc_divider_clk *to_ultrarisc_divider_clk(struct clk_hw *hw) +{ + struct clk_divider *divider = to_clk_divider(hw); + + return container_of(divider, struct ultrarisc_divider_clk, divider); +} + +static unsigned long ultrarisc_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ultrarisc_pll_clk *pll = to_ultrarisc_pll_clk(hw); + const struct ultrarisc_pll_layout *layout = pll->layout; + u32 oddiv1_div, oddiv2_div; + u64 mult, rate, den; + u32 frac, m, n; + u32 cfg1, cfg2; + + cfg1 = readl_relaxed(pll->base + layout->cfg1_offset); + cfg2 = readl_relaxed(pll->base + layout->cfg2_offset); + + frac = field_get(layout->frac_mask, cfg1); + m = field_get(layout->m_mask, cfg2); + n = field_get(layout->n_mask, cfg2); + if (!n) + return 0; + + oddiv1_div = 1U << field_get(layout->oddiv1_mask, cfg2); + oddiv2_div = 1U << field_get(layout->oddiv2_mask, cfg2); + + /* + * The output frequency is calculated as: + * fvco = parent * (m + frac / 2^24) / n + * fout = fvco / (2^oddiv1_raw * 2^oddiv2_raw) + * + * The output divider values are derived from the raw register field values as: + * oddivX_div = 1 << oddivX_raw + */ + mult = ((u64)m << 24) + frac; + rate = (u64)parent_rate * mult; + den = ((u64)n << 24) * oddiv1_div * oddiv2_div; + + return div64_u64(rate + (den >> 1), den); +} + +static const struct clk_ops ultrarisc_pll_ro_ops = { + .recalc_rate = ultrarisc_pll_recalc_rate, +}; + +static unsigned long ultrarisc_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + u32 val; + + val = readl_relaxed(divider->reg) >> divider->shift; + val &= clk_div_mask(divider->width); + + return divider_recalc_rate(hw, parent_rate, val, divider->table, + divider->flags, divider->width); +} + +static int ultrarisc_divider_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_divider *divider = to_clk_divider(hw); + + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); +} + +static int ultrarisc_divider_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ultrarisc_divider_clk *divider_clk = to_ultrarisc_divider_clk(hw); + struct clk_divider *divider = ÷r_clk->divider; + int value; + u32 val; + + value = divider_get_val(rate, parent_rate, divider->table, + divider->width, divider->flags); + if (value < 0) + return value; + + scoped_guard(spinlock_irqsave, divider->lock) { + val = readl_relaxed(divider->reg); + val &= ~(clk_div_mask(divider->width) << divider->shift); + val |= value << divider->shift; + writel_relaxed(val, divider->reg); + + if (divider_clk->load_mask) { + /* + * Program the new divider field, then write 1 to the + * load bit to trigger the update. The load bit is + * write-triggered and reads back as 0 on this hardware. + */ + writel_relaxed(val | divider_clk->load_mask, divider->reg); + } + } + + return 0; +} + +static const struct clk_ops ultrarisc_divider_ops = { + .recalc_rate = ultrarisc_divider_recalc_rate, + .determine_rate = ultrarisc_divider_determine_rate, + .set_rate = ultrarisc_divider_set_rate, +}; + +static struct clk_hw *ultrarisc_clk_register_pll(struct device *dev, + const struct ultrarisc_pll_desc *desc, + const struct ultrarisc_pll_layout *layout, + void __iomem *base) +{ + struct clk_parent_data pdata = { .index = 0 }; + struct ultrarisc_pll_clk *pll; + struct clk_init_data init = { + .name = desc->name, + .ops = &ultrarisc_pll_ro_ops, + .parent_data = &pdata, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + }; + int ret; + + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + pll->base = base; + pll->layout = layout; + pll->hw.init = &init; + + ret = devm_clk_hw_register(dev, &pll->hw); + if (ret) + return ERR_PTR(ret); + + return &pll->hw; +} + +static struct clk_hw * +ultrarisc_clk_register_divider(struct device *dev, + const struct ultrarisc_divider_desc *desc, + struct clk_hw *parent_hw, void __iomem *base, + spinlock_t *lock) +{ + const struct clk_parent_data pdata = { .hw = parent_hw }; + void __iomem *reg = base + desc->offset; + struct ultrarisc_divider_clk *divider; + + if (!desc->div_width) + return ERR_PTR(-EINVAL); + + if (!lock) + return ERR_PTR(-EINVAL); + + divider = devm_kzalloc(dev, sizeof(*divider), GFP_KERNEL); + if (!divider) + return ERR_PTR(-ENOMEM); + + divider->divider.reg = reg; + divider->divider.shift = desc->div_shift; + divider->divider.width = desc->div_width; + divider->divider.flags = desc->divider_flags; + divider->divider.lock = lock; + divider->load_mask = desc->load_mask; + divider->gate.reg = reg; + divider->gate.bit_idx = desc->gate_bit; + divider->gate.flags = desc->gate_flags; + divider->gate.lock = lock; + + return devm_clk_hw_register_composite_pdata(dev, desc->name, + &pdata, 1, NULL, NULL, + ÷r->divider.hw, + &ultrarisc_divider_ops, + ÷r->gate.hw, + &clk_gate_ops, 0); +} + +static int ultrarisc_clk_register_fixed_factors(struct device *dev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data) +{ + u32 i; + + for (i = 0; i < soc_data->num_fixed_factors; i++) { + const struct ultrarisc_fixed_factor_desc *desc; + struct clk_hw *parent_hw; + struct clk_hw *hw; + + desc = &soc_data->fixed_factors[i]; + if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num) + return -EINVAL; + + parent_hw = clk_data->hws[desc->parent_id]; + if (!parent_hw) + return -EINVAL; + + hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, desc->name, + parent_hw, 0, + desc->mult, + desc->div); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +static int ultrarisc_clk_register_plls(struct platform_device *pdev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data, + void __iomem *base) +{ + struct device *dev = &pdev->dev; + u32 i; + + for (i = 0; i < soc_data->num_plls; i++) { + const struct ultrarisc_pll_desc *desc = &soc_data->plls[i]; + struct clk_hw *hw; + + if (desc->id >= clk_data->num) { + dev_err(dev, "%s invalid clock ID %u >= %u\n", + desc->name, desc->id, clk_data->num); + return -EINVAL; + } + + hw = ultrarisc_clk_register_pll(dev, desc, soc_data->pll_layout, base); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +static int ultrarisc_clk_register_dividers(struct platform_device *pdev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data, + void __iomem *base, + spinlock_t *lock) +{ + struct device *dev = &pdev->dev; + u32 i; + + for (i = 0; i < soc_data->num_dividers; i++) { + const struct ultrarisc_divider_desc *desc; + struct clk_hw *parent_hw; + struct clk_hw *hw; + + desc = &soc_data->dividers[i]; + if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num) + return -EINVAL; + + parent_hw = clk_data->hws[desc->parent_id]; + if (!parent_hw) + return -EINVAL; + + hw = ultrarisc_clk_register_divider(dev, desc, parent_hw, base, + lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + if (desc->max_rate) + clk_hw_set_rate_range(hw, 0, desc->max_rate); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +static int ultrarisc_clk_register_gates(struct platform_device *pdev, + struct clk_hw_onecell_data *clk_data, + const struct ultrarisc_clk_soc_data *soc_data, + void __iomem *base, + spinlock_t *lock) +{ + struct device *dev = &pdev->dev; + u32 i; + + for (i = 0; i < soc_data->num_gates; i++) { + const struct ultrarisc_gate_desc *desc; + struct clk_hw *parent_hw; + struct clk_hw *hw; + + desc = &soc_data->gates[i]; + if (desc->id >= clk_data->num || desc->parent_id >= clk_data->num) + return -EINVAL; + + parent_hw = clk_data->hws[desc->parent_id]; + if (!parent_hw) + return -EINVAL; + + hw = devm_clk_hw_register_gate_parent_hw(dev, desc->name, + parent_hw, 0, + base + desc->offset, + desc->gate_bit, + desc->gate_flags, + lock); + if (IS_ERR(hw)) + return PTR_ERR(hw); + + clk_data->hws[desc->id] = hw; + } + + return 0; +} + +int ultrarisc_clk_probe(struct platform_device *pdev, + const struct ultrarisc_clk_soc_data *soc_data) +{ + struct clk_hw_onecell_data *clk_data; + struct device *dev = &pdev->dev; + void __iomem *base; + spinlock_t *lock; + int ret; + + if (!soc_data) + return -EINVAL; + + lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); + if (!lock) + return -ENOMEM; + + spin_lock_init(lock); + + clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, + soc_data->num_clks), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->num = soc_data->num_clks; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = ultrarisc_clk_register_plls(pdev, clk_data, soc_data, base); + if (ret) + return ret; + + ret = ultrarisc_clk_register_fixed_factors(dev, clk_data, soc_data); + if (ret) + return ret; + + ret = ultrarisc_clk_register_dividers(pdev, clk_data, soc_data, base, lock); + if (ret) + return ret; + + ret = ultrarisc_clk_register_gates(pdev, clk_data, soc_data, base, lock); + if (ret) + return ret; + + for (int i = 0; i < clk_data->num; i++) { + if (!clk_data->hws[i]) { + dev_err(dev, "missing clock ID %u\n", i); + return -EINVAL; + } + } + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); +} +EXPORT_SYMBOL_NS_GPL(ultrarisc_clk_probe, "CLK_ULTRARISC"); + +MODULE_DESCRIPTION("UltraRISC clock core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/ultrarisc/clk-ultrarisc.h b/drivers/clk/ultrarisc/clk-ultrarisc.h new file mode 100644 index 000000000000..8202b7ed0e31 --- /dev/null +++ b/drivers/clk/ultrarisc/clk-ultrarisc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __ULTRARISC_CLK_ULTRARISC_H +#define __ULTRARISC_CLK_ULTRARISC_H + +#include +#include +#include + +struct ultrarisc_pll_layout { + u32 cfg1_offset; + u32 cfg2_offset; + u32 frac_mask; + u32 m_mask; + u32 n_mask; + u32 oddiv1_mask; + u32 oddiv2_mask; +}; + +struct ultrarisc_pll_desc { + u32 id; + const char *name; +}; + +struct ultrarisc_fixed_factor_desc { + u32 id; + const char *name; + u32 parent_id; + u32 mult; + u32 div; +}; + +struct ultrarisc_divider_desc { + u32 id; + const char *name; + u32 offset; + u32 parent_id; + unsigned long max_rate; + u32 load_mask; + u8 div_shift; + u8 div_width; + u8 gate_bit; + u16 divider_flags; + u8 gate_flags; +}; + +struct ultrarisc_gate_desc { + u32 id; + const char *name; + u32 offset; + u32 parent_id; + u8 gate_bit; + u8 gate_flags; +}; + +struct ultrarisc_clk_soc_data { + const struct ultrarisc_pll_layout *pll_layout; + const struct ultrarisc_pll_desc *plls; + u32 num_plls; + const struct ultrarisc_fixed_factor_desc *fixed_factors; + u32 num_fixed_factors; + const struct ultrarisc_divider_desc *dividers; + u32 num_dividers; + const struct ultrarisc_gate_desc *gates; + u32 num_gates; + u32 num_clks; +}; + +int ultrarisc_clk_probe(struct platform_device *pdev, + const struct ultrarisc_clk_soc_data *soc_data); + +#endif /* __ULTRARISC_CLK_ULTRARISC_H */ From 48ceae43be9f3537eee179bcc53266eadbc40bcc Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:05 +0800 Subject: [PATCH 24/45] dt-bindings: soc: cix: add sky1 audss cru controller The Cix Sky1 Audio Subsystem (AUDSS) Clock and Reset Unit (CRU) groups clock muxing, gating and block-level software reset control in a single register block. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Joakim Zhang Signed-off-by: Brian Masney --- .../bindings/soc/cix/cix,sky1-audss-cru.yaml | 92 +++++++++++++++++++ .../dt-bindings/clock/cix,sky1-audss-cru.h | 60 ++++++++++++ .../dt-bindings/reset/cix,sky1-audss-cru.h | 25 +++++ 3 files changed, 177 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml create mode 100644 include/dt-bindings/clock/cix,sky1-audss-cru.h create mode 100644 include/dt-bindings/reset/cix,sky1-audss-cru.h diff --git a/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml new file mode 100644 index 000000000000..50dd0593e1d9 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/cix/cix,sky1-audss-cru.yaml @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/cix/cix,sky1-audss-cru.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cix Sky1 audio subsystem clock and reset unit + +maintainers: + - Joakim Zhang + +description: | + The Cix Sky1 Audio Subsystem (AUDSS) Clock and Reset Unit (CRU) groups + audio-related clock muxing, gating and block-level software reset control + in a single register block. + + A single device node exposes both the clock controller and software reset + lines. The clock driver registers as a platform driver; the reset controller + is registered by an auxiliary driver bound from the clock driver. + + Four SoC-level reference clocks listed in clocks/clock-names feed the AUDSS + clock tree. Internal AUDSS clocks are exposed via #clock-cells; indices are + defined in include/dt-bindings/clock/cix,sky1-audss-cru.h. + + Block-level software reset indices are exposed via #reset-cells; indices + are defined in include/dt-bindings/reset/cix,sky1-audss-cru.h. + + The SoC syscon NoC (or bus) reset is described via resets. The audio + subsystem power domain is described via power-domains. + +properties: + compatible: + const: cix,sky1-audss-cru + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + description: + Clock indices are defined in include/dt-bindings/clock/cix,sky1-audss-cru.h. + + '#reset-cells': + const: 1 + description: + Reset indices are defined in include/dt-bindings/reset/cix,sky1-audss-cru.h. + + clocks: + items: + - description: I2S parent clock for sampling rates multiple of 8kHz. + - description: I2S parent clock for sampling rates multiple of 11.025kHz. + - description: Clock feeding most devices in AUDSS (NOC, DSP, SRAM, HDA, DMAC, I2S, and mailbox). + - description: Clock feeding HDA, timer and watchdog, which is a dedicated 48 MHz clock. + + clock-names: + items: + - const: x8k + - const: x11k + - const: sys + - const: 48m + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + +required: + - compatible + - reg + - '#clock-cells' + - '#reset-cells' + - clocks + - clock-names + - power-domains + - resets + +additionalProperties: false + +examples: + - | + audss_cru: clock-controller@7110000 { + compatible = "cix,sky1-audss-cru"; + reg = <0x7110000 0x10000>; + #clock-cells = <1>; + #reset-cells = <1>; + clocks = <&scmi_clk 76>, <&scmi_clk 78>, + <&scmi_clk 70>, <&scmi_clk 71>; + clock-names = "x8k", "x11k", "sys", "48m"; + power-domains = <&smc_devpd 0>; + resets = <&s5_syscon 31>; + }; diff --git a/include/dt-bindings/clock/cix,sky1-audss-cru.h b/include/dt-bindings/clock/cix,sky1-audss-cru.h new file mode 100644 index 000000000000..8c58ef8bf682 --- /dev/null +++ b/include/dt-bindings/clock/cix,sky1-audss-cru.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright 2026 Cix Technology Group Co., Ltd. + */ + +#ifndef _DT_BINDINGS_CLOCK_CIX_SKY1_AUDSS_CRU_H +#define _DT_BINDINGS_CLOCK_CIX_SKY1_AUDSS_CRU_H + +#define CLK_AUD_CLK4_DIV2 0 +#define CLK_AUD_CLK4_DIV4 1 +#define CLK_AUD_CLK5_DIV2 2 + +#define CLK_DSP_CLK 3 +#define CLK_DSP_BCLK 4 +#define CLK_DSP_PBCLK 5 + +#define CLK_SRAM_AXI 6 + +#define CLK_HDA_SYS 7 +#define CLK_HDA_HDA 8 + +#define CLK_DMAC_AXI 9 + +#define CLK_WDG_APB 10 +#define CLK_WDG_WDG 11 + +#define CLK_TIMER_APB 12 +#define CLK_TIMER_TIMER 13 + +#define CLK_MB_0_APB 14 /* MB0: ap->dsp */ +#define CLK_MB_1_APB 15 /* MB1: dsp->ap */ + +#define CLK_I2S0_APB 16 +#define CLK_I2S1_APB 17 +#define CLK_I2S2_APB 18 +#define CLK_I2S3_APB 19 +#define CLK_I2S4_APB 20 +#define CLK_I2S5_APB 21 +#define CLK_I2S6_APB 22 +#define CLK_I2S7_APB 23 +#define CLK_I2S8_APB 24 +#define CLK_I2S9_APB 25 +#define CLK_I2S0 26 +#define CLK_I2S1 27 +#define CLK_I2S2 28 +#define CLK_I2S3 29 +#define CLK_I2S4 30 +#define CLK_I2S5 31 +#define CLK_I2S6 32 +#define CLK_I2S7 33 +#define CLK_I2S8 34 +#define CLK_I2S9 35 + +#define CLK_MCLK0 36 +#define CLK_MCLK1 37 +#define CLK_MCLK2 38 +#define CLK_MCLK3 39 +#define CLK_MCLK4 40 + +#endif diff --git a/include/dt-bindings/reset/cix,sky1-audss-cru.h b/include/dt-bindings/reset/cix,sky1-audss-cru.h new file mode 100644 index 000000000000..55e9f3797b30 --- /dev/null +++ b/include/dt-bindings/reset/cix,sky1-audss-cru.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright 2026 Cix Technology Group Co., Ltd. + */ +#ifndef DT_BINDINGS_RESET_CIX_SKY1_AUDSS_CRU_H +#define DT_BINDINGS_RESET_CIX_SKY1_AUDSS_CRU_H + +#define AUDSS_I2S0_SW_RST 0 +#define AUDSS_I2S1_SW_RST 1 +#define AUDSS_I2S2_SW_RST 2 +#define AUDSS_I2S3_SW_RST 3 +#define AUDSS_I2S4_SW_RST 4 +#define AUDSS_I2S5_SW_RST 5 +#define AUDSS_I2S6_SW_RST 6 +#define AUDSS_I2S7_SW_RST 7 +#define AUDSS_I2S8_SW_RST 8 +#define AUDSS_I2S9_SW_RST 9 +#define AUDSS_WDT_SW_RST 10 +#define AUDSS_TIMER_SW_RST 11 +#define AUDSS_MB0_SW_RST 12 +#define AUDSS_MB1_SW_RST 13 +#define AUDSS_HDA_SW_RST 14 +#define AUDSS_DMAC_SW_RST 15 + +#endif From 3730fe6032d9a5e1140994693feefe0efa927acb Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:06 +0800 Subject: [PATCH 25/45] clk: cix: add sky1 audss clock controller Add a platform driver for the Cix Sky1 AUDSS CRU. The driver maps the CRU registers and registers mux, divider and gate clocks for DSP, SRAM, HDA, DMAC, I2S, mailbox, watchdog and timer blocks. Four SoC-level audio reference clocks are enabled as inputs to the internal clock tree. The driver releases the AUDSS NOC reset, enables runtime PM and instantiates the auxiliary reset device. Signed-off-by: Joakim Zhang Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/cix/Kconfig | 16 + drivers/clk/cix/Makefile | 3 + drivers/clk/cix/clk-sky1-audss.c | 1206 ++++++++++++++++++++++++++++++ 5 files changed, 1227 insertions(+) create mode 100644 drivers/clk/cix/Kconfig create mode 100644 drivers/clk/cix/Makefile create mode 100644 drivers/clk/cix/clk-sky1-audss.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index f83dda6ffc88..057feabf2f23 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -509,6 +509,7 @@ source "drivers/clk/actions/Kconfig" source "drivers/clk/analogbits/Kconfig" source "drivers/clk/aspeed/Kconfig" source "drivers/clk/bcm/Kconfig" +source "drivers/clk/cix/Kconfig" source "drivers/clk/eswin/Kconfig" source "drivers/clk/hisilicon/Kconfig" source "drivers/clk/imgtec/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index dc56d0249450..0b6241305d4e 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_ARCH_ARTPEC) += axis/ obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/ obj-y += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ +obj-y += cix/ obj-$(CONFIG_ARCH_DAVINCI) += davinci/ obj-$(CONFIG_COMMON_CLK_ESWIN) += eswin/ obj-$(CONFIG_ARCH_HISI) += hisilicon/ diff --git a/drivers/clk/cix/Kconfig b/drivers/clk/cix/Kconfig new file mode 100644 index 000000000000..b057ef85fab6 --- /dev/null +++ b/drivers/clk/cix/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# Audio subsystem clock support for Cixtech SoC family +menu "Cixtech Audio Subsystem Clock Driver" + +config CLK_SKY1_AUDSS + tristate "Cixtech Sky1 Audio Subsystem Clock Driver" + depends on ARCH_CIX || COMPILE_TEST + select AUXILIARY_BUS + select REGMAP_MMIO + select RESET_CONTROLLER + help + Support for the Audio Subsystem clock controller present on + Cixtech Sky1 SoC. This driver provides mux, divider and gate + clocks for DSP, I2S, HDA and related blocks in the audio + subsystem. Say M or Y here if you want to build this driver. +endmenu diff --git a/drivers/clk/cix/Makefile b/drivers/clk/cix/Makefile new file mode 100644 index 000000000000..bc612f1d08b2 --- /dev/null +++ b/drivers/clk/cix/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_CLK_SKY1_AUDSS) += clk-sky1-audss.o diff --git a/drivers/clk/cix/clk-sky1-audss.c b/drivers/clk/cix/clk-sky1-audss.c new file mode 100644 index 000000000000..e096b8121df3 --- /dev/null +++ b/drivers/clk/cix/clk-sky1-audss.c @@ -0,0 +1,1206 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2026 Cix Technology Group Co., Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define INFO_HIFI0 0x00 +#define INFO_CLK_GATE 0x10 +#define INFO_CLK_DIV 0x14 +#define INFO_CLK_MUX 0x18 +#define INFO_MCLK 0x70 + +#define SKY1_AUDSS_CLK_PARENTS_CNT 4 +#define SKY1_AUDSS_NUM_CLKS (CLK_MCLK4 + 1) + +static u32 sky1_reg_save[][2] = { + { INFO_HIFI0, 0 }, + { INFO_CLK_GATE, 0 }, + { INFO_CLK_DIV, 0 }, + { INFO_CLK_MUX, 0 }, + { INFO_MCLK, 0 }, +}; + +static const char * const sky1_audss_clk_names[SKY1_AUDSS_CLK_PARENTS_CNT] = { + "x8k", "x11k", "sys", "48m", +}; + +static const u32 sky1_clk_rate_default[SKY1_AUDSS_CLK_PARENTS_CNT] = { + 294912000, + 270950400, + 800000000, + 48000000, +}; + +static const char * const dsp_clk_parent[] = { + "audio_clk4" +}; + +static const char * const dsp_bclk_parent[] = { + "audio_clk4_div2" +}; + +static const char * const dsp_pbclk_parent[] = { + "audio_clk4_div4" +}; + +static const char * const sram_axi_parent[] = { + "audio_clk4_div2" +}; + +static const char * const hda_sys_parent[] = { + "audio_clk4_div2" +}; + +static const char * const hda_hda_parent[] = { + "audio_clk5" +}; + +static const char * const dmac_axi_parent[] = { + "audio_clk4_div2" +}; + +static const char * const wdg_apb_parent[] = { + "audio_clk5_div2" +}; + +static const char * const wdg_wdg_parent[] = { + "audio_clk5_div2" +}; + +static const char * const timer_apb_parent[] = { + "audio_clk4_div4" +}; + +static const char * const timer_timer_parent[] = { + "audio_clk5_div2" +}; + +static const char * const mailbox_apb_parent[] = { + "audio_clk4_div4" +}; + +static const char * const i2s_apb_parent[] = { + "audio_clk4_div4" +}; + +static const char * const i2s0_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s1_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s2_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s3_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s4_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s5_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s6_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s7_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s8_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const i2s9_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const char * const mclk_parents[] = { + "audio_clk0", "audio_clk2" +}; + +static const u32 i2s3_mux_table[] = { 0, 2 }; +static const u32 i2s4_mux_table[] = { 0, 2 }; + +/* + * audss composite clock definition + */ +struct muxdiv_cfg { + int offset; + u8 shift; + u8 width; + u8 flags; +}; + +struct gate_cfg { + int offset; + u8 shift; + u8 flags; +}; + +struct composite_clk_cfg { + u32 id; + const char * const name; + const char * const *parent_names; + int num_parents; + const u32 *mux_table; + struct muxdiv_cfg *mux_cfg; + struct muxdiv_cfg *div_cfg; + struct gate_cfg *gate_cfg; + unsigned long flags; +}; + +#define CFG(_id,\ + _name,\ + _parent_names,\ + _mux_table,\ + _mux_offset, _mux_shift, _mux_width, _mux_flags,\ + _div_offset, _div_shift, _div_width, _div_flags,\ + _gate_offset, _gate_shift, _gate_flags,\ + _flags)\ +{\ + .id = _id,\ + .name = _name,\ + .parent_names = _parent_names,\ + .num_parents = ARRAY_SIZE(_parent_names),\ + .mux_table = _mux_table,\ + .mux_cfg = &(struct muxdiv_cfg) { _mux_offset, _mux_shift, _mux_width, _mux_flags },\ + .div_cfg = &(struct muxdiv_cfg) { _div_offset, _div_shift, _div_width, _div_flags },\ + .gate_cfg = &(struct gate_cfg) { _gate_offset, _gate_shift, _gate_flags },\ + .flags = _flags,\ +} + +static const struct composite_clk_cfg sky1_audss_clks[] = { + /* dsp */ + CFG(CLK_DSP_CLK, + "audss_dsp_clk", + dsp_clk_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_HIFI0, 0, 0, + 0), + CFG(CLK_DSP_BCLK, + "audss_dsp_bclk", + dsp_bclk_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + -1, 0, 0, + 0), + CFG(CLK_DSP_PBCLK, + "audss_dsp_pbclk", + dsp_pbclk_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + -1, 0, 0, + 0), + /* sram */ + CFG(CLK_SRAM_AXI, + "audss_sram_axi", + sram_axi_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 16, 0, + 0), + /* hda */ + CFG(CLK_HDA_SYS, + "audss_hda_sys", + hda_sys_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 14, 0, + 0), + CFG(CLK_HDA_HDA, + "audss_hda_hda", + hda_hda_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 14, 0, + 0), + /* dmac */ + CFG(CLK_DMAC_AXI, + "audss_dmac_axi", + dmac_axi_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 15, 0, + 0), + /* wdg */ + CFG(CLK_WDG_APB, + "audss_wdg_apb", + wdg_apb_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 10, 0, + 0), + CFG(CLK_WDG_WDG, + "audss_wdg_wdg", + wdg_wdg_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 10, 0, + 0), + /* timer */ + CFG(CLK_TIMER_APB, + "audss_timer_apb", + timer_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 11, 0, + 0), + CFG(CLK_TIMER_TIMER, + "audss_timer_timer", + timer_timer_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 11, 0, + 0), + /* mailbox: mb0(ap->dsp), mb1(dsp->ap) */ + CFG(CLK_MB_0_APB, + "audss_mb_0_apb", + mailbox_apb_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 12, 0, + 0), + CFG(CLK_MB_1_APB, + "audss_mb_1_apb", + mailbox_apb_parent, + NULL, + -1, 0, 0, 0, + -1, 0, 0, 0, + INFO_CLK_GATE, 13, 0, + 0), + /* i2s */ + CFG(CLK_I2S0_APB, + "audss_i2s0_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 0, 0, + 0), + CFG(CLK_I2S1_APB, + "audss_i2s1_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 1, 0, + 0), + CFG(CLK_I2S2_APB, + "audss_i2s2_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 2, 0, + 0), + CFG(CLK_I2S3_APB, + "audss_i2s3_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 3, 0, + 0), + CFG(CLK_I2S4_APB, + "audss_i2s4_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 4, 0, + 0), + CFG(CLK_I2S5_APB, + "audss_i2s5_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 5, 0, + 0), + CFG(CLK_I2S6_APB, + "audss_i2s6_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 6, 0, + 0), + CFG(CLK_I2S7_APB, + "audss_i2s7_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 7, 0, + 0), + CFG(CLK_I2S8_APB, + "audss_i2s8_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 8, 0, + 0), + CFG(CLK_I2S9_APB, + "audss_i2s9_apb", + i2s_apb_parent, + NULL, + -1, 0, 0, 0, + INFO_CLK_DIV, 0, 2, 0, + INFO_CLK_GATE, 9, 0, + 0), + CFG(CLK_I2S0, + "audss_i2s0", + i2s0_parents, + NULL, + INFO_CLK_MUX, 0, 2, 0, + INFO_CLK_DIV, 2, 2, 0, + INFO_CLK_GATE, 0, 0, + 0), + CFG(CLK_I2S1, + "audss_i2s1", + i2s1_parents, + NULL, + INFO_CLK_MUX, 2, 2, 0, + INFO_CLK_DIV, 4, 2, 0, + INFO_CLK_GATE, 1, 0, + 0), + CFG(CLK_I2S2, + "audss_i2s2", + i2s2_parents, + NULL, + INFO_CLK_MUX, 4, 2, 0, + INFO_CLK_DIV, 6, 2, 0, + INFO_CLK_GATE, 2, 0, + 0), + CFG(CLK_I2S3, + "audss_i2s3", + i2s3_parents, + i2s3_mux_table, + INFO_CLK_MUX, 6, 2, 0, + INFO_CLK_DIV, 8, 2, 0, + INFO_CLK_GATE, 3, 0, + 0), + CFG(CLK_I2S4, + "audss_i2s4", + i2s4_parents, + i2s4_mux_table, + INFO_CLK_MUX, 8, 2, 0, + INFO_CLK_DIV, 10, 2, 0, + INFO_CLK_GATE, 4, 0, + 0), + CFG(CLK_I2S5, + "audss_i2s5", + i2s5_parents, + NULL, + INFO_CLK_MUX, 10, 2, 0, + INFO_CLK_DIV, 12, 2, 0, + INFO_CLK_GATE, 5, 0, + 0), + CFG(CLK_I2S6, + "audss_i2s6", + i2s6_parents, + NULL, + INFO_CLK_MUX, 12, 2, 0, + INFO_CLK_DIV, 14, 2, 0, + INFO_CLK_GATE, 6, 0, + 0), + CFG(CLK_I2S7, + "audss_i2s7", + i2s7_parents, + NULL, + INFO_CLK_MUX, 14, 2, 0, + INFO_CLK_DIV, 16, 2, 0, + INFO_CLK_GATE, 7, 0, + 0), + CFG(CLK_I2S8, + "audss_i2s8", + i2s8_parents, + NULL, + INFO_CLK_MUX, 16, 2, 0, + INFO_CLK_DIV, 18, 2, 0, + INFO_CLK_GATE, 8, 0, + 0), + CFG(CLK_I2S9, + "audss_i2s9", + i2s9_parents, + NULL, + INFO_CLK_MUX, 18, 2, 0, + INFO_CLK_DIV, 20, 2, 0, + INFO_CLK_GATE, 9, 0, + 0), + /* mclk */ + CFG(CLK_MCLK0, + "audss_mclk0", + mclk_parents, + NULL, + INFO_MCLK, 5, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 0, 0, + 0), + CFG(CLK_MCLK1, + "audss_mclk1", + mclk_parents, + NULL, + INFO_MCLK, 6, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 1, 0, + 0), + CFG(CLK_MCLK2, + "audss_mclk2", + mclk_parents, + NULL, + INFO_MCLK, 7, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 2, 0, + 0), + CFG(CLK_MCLK3, + "audss_mclk3", + mclk_parents, + NULL, + INFO_MCLK, 8, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 3, 0, + 0), + CFG(CLK_MCLK4, + "audss_mclk4", + mclk_parents, + NULL, + INFO_MCLK, 9, 1, 0, + -1, 0, 0, 0, + INFO_MCLK, 4, 0, + 0), +}; + +struct sky1_audss_clks_devtype_data { + u32 (*reg_save)[2]; + size_t reg_save_size; + const char * const *clk_names; + size_t clk_num; + const u32 *clk_rate_default; + const struct composite_clk_cfg *clk_cfg; + size_t clk_cfg_size; +}; + +static const struct regmap_config sky1_audss_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +struct sky1_audss_clks_priv { + struct device *dev; + struct regmap *regmap_cru; + struct reset_control *rst_noc; + struct clk *clks[SKY1_AUDSS_CLK_PARENTS_CNT]; + const struct sky1_audss_clks_devtype_data *devtype_data; + spinlock_t lock; + struct clk_hw_onecell_data *clk_data; +}; + +#if IS_ENABLED(CONFIG_RESET_SKY1_AUDSS) + +static int sky1_audss_reset_controller_register(struct device *dev) +{ + struct auxiliary_device *adev; + + if (!of_property_present(dev->of_node, "#reset-cells")) + return 0; + + adev = devm_auxiliary_device_create(dev, "reset", NULL); + if (!adev) + return -ENODEV; + + return 0; +} + +#else + +static int sky1_audss_reset_controller_register(struct device *dev) +{ + return 0; +} + +#endif + +/* + * clk_ops for audss clock mux/divider/gate + */ +struct sky1_clk_divider { + struct clk_divider div; + struct regmap *regmap; + int offset; +}; + +struct sky1_clk_gate { + struct clk_gate gate; + struct regmap *regmap; + int offset; +}; + +struct sky1_clk_mux { + struct clk_mux mux; + struct regmap *regmap; + int offset; +}; + +static inline struct sky1_clk_mux *to_sky1_clk_mux(struct clk_mux *mux) +{ + return container_of(mux, struct sky1_clk_mux, mux); +} + +static u8 sky1_audss_clk_mux_get_parent(struct clk_hw *hw) +{ + struct clk_mux *mux = to_clk_mux(hw); + struct sky1_clk_mux *sky1_mux = to_sky1_clk_mux(mux); + u32 val; + + regmap_read(sky1_mux->regmap, sky1_mux->offset, &val); + val = val >> mux->shift; + val &= mux->mask; + + return clk_mux_val_to_index(hw, mux->table, mux->flags, val); +} + +static int sky1_audss_clk_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_mux *mux = to_clk_mux(hw); + u32 val = clk_mux_index_to_val(mux->table, mux->flags, index); + struct sky1_clk_mux *sky1_mux = to_sky1_clk_mux(mux); + unsigned long flags = 0; + u32 reg; + + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + else + __acquire(mux->lock); + + if (mux->flags & CLK_MUX_HIWORD_MASK) { + reg = mux->mask << (mux->shift + 16); + } else { + regmap_read(sky1_mux->regmap, sky1_mux->offset, ®); + reg &= ~(mux->mask << mux->shift); + } + val = val << mux->shift; + reg |= val; + regmap_write(sky1_mux->regmap, sky1_mux->offset, reg); + + if (mux->lock) + spin_unlock_irqrestore(mux->lock, flags); + else + __release(mux->lock); + + return 0; +} + +static int sky1_audss_clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_mux *mux = to_clk_mux(hw); + + return clk_mux_determine_rate_flags(hw, req, mux->flags); +} + +static const struct clk_ops sky1_audss_clk_mux_ops = { + .get_parent = sky1_audss_clk_mux_get_parent, + .set_parent = sky1_audss_clk_mux_set_parent, + .determine_rate = sky1_audss_clk_mux_determine_rate, +}; + +static inline struct sky1_clk_divider *to_sky1_clk_divider(struct clk_divider *div) +{ + return container_of(div, struct sky1_clk_divider, div); +} + +static unsigned long sky1_audss_clk_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider); + unsigned int val; + + regmap_read(sky1_div->regmap, sky1_div->offset, &val); + val = val >> divider->shift; + val &= clk_div_mask(divider->width); + + return divider_recalc_rate(hw, parent_rate, val, divider->table, + divider->flags, divider->width); +} + +static int sky1_audss_clk_divider_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_divider *divider = to_clk_divider(hw); + struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider); + + /* if read only, just return current value */ + if (divider->flags & CLK_DIVIDER_READ_ONLY) { + u32 val; + + regmap_read(sky1_div->regmap, sky1_div->offset, &val); + val = val >> divider->shift; + val &= clk_div_mask(divider->width); + + return divider_ro_determine_rate(hw, req, divider->table, + divider->width, + divider->flags, val); + } + + return divider_determine_rate(hw, req, divider->table, divider->width, + divider->flags); +} + +static int sky1_audss_clk_divider_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct clk_divider *divider = to_clk_divider(hw); + struct sky1_clk_divider *sky1_div = to_sky1_clk_divider(divider); + int value; + unsigned long flags = 0; + u32 val; + + value = divider_get_val(rate, parent_rate, divider->table, + divider->width, divider->flags); + if (value < 0) + return value; + + if (divider->lock) + spin_lock_irqsave(divider->lock, flags); + else + __acquire(divider->lock); + + if (divider->flags & CLK_DIVIDER_HIWORD_MASK) { + val = clk_div_mask(divider->width) << (divider->shift + 16); + } else { + regmap_read(sky1_div->regmap, sky1_div->offset, &val); + val &= ~(clk_div_mask(divider->width) << divider->shift); + } + val |= (u32)value << divider->shift; + regmap_write(sky1_div->regmap, sky1_div->offset, val); + + if (divider->lock) + spin_unlock_irqrestore(divider->lock, flags); + else + __release(divider->lock); + + return 0; +} + +static const struct clk_ops sky1_audss_clk_divider_ops = { + .recalc_rate = sky1_audss_clk_divider_recalc_rate, + .determine_rate = sky1_audss_clk_divider_determine_rate, + .set_rate = sky1_audss_clk_divider_set_rate, +}; + +static inline struct sky1_clk_gate *to_sky1_clk_gate(struct clk_gate *gate) +{ + return container_of(gate, struct sky1_clk_gate, gate); +} + +static void sky1_audss_clk_gate_endisable(struct clk_hw *hw, int enable) +{ + struct clk_gate *gate = to_clk_gate(hw); + struct sky1_clk_gate *sky1_gate = to_sky1_clk_gate(gate); + int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0; + unsigned long flags = 0; + u32 reg; + + set ^= enable; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + else + __acquire(gate->lock); + + if (gate->flags & CLK_GATE_HIWORD_MASK) { + reg = BIT(gate->bit_idx + 16); + if (set) + reg |= BIT(gate->bit_idx); + } else { + regmap_read(sky1_gate->regmap, sky1_gate->offset, ®); + + if (set) + reg |= BIT(gate->bit_idx); + else + reg &= ~BIT(gate->bit_idx); + } + + regmap_write(sky1_gate->regmap, sky1_gate->offset, reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); + else + __release(gate->lock); +} + +static int sky1_audss_clk_gate_enable(struct clk_hw *hw) +{ + sky1_audss_clk_gate_endisable(hw, 1); + + return 0; +} + +static void sky1_audss_clk_gate_disable(struct clk_hw *hw) +{ + sky1_audss_clk_gate_endisable(hw, 0); +} + +static int sky1_audss_clk_gate_is_enabled(struct clk_hw *hw) +{ + struct clk_gate *gate = to_clk_gate(hw); + struct sky1_clk_gate *sky1_gate = to_sky1_clk_gate(gate); + u32 reg; + + regmap_read(sky1_gate->regmap, sky1_gate->offset, ®); + + /* if a set bit disables this clk, flip it before masking */ + if (gate->flags & CLK_GATE_SET_TO_DISABLE) + reg ^= BIT(gate->bit_idx); + + reg &= BIT(gate->bit_idx); + + return !!reg; +} + +static const struct clk_ops sky1_audss_clk_gate_ops = { + .enable = sky1_audss_clk_gate_enable, + .disable = sky1_audss_clk_gate_disable, + .is_enabled = sky1_audss_clk_gate_is_enabled, +}; + +static struct clk_hw *sky1_audss_clk_register(struct device *dev, + const char *name, + const char * const *parent_names, + int num_parents, + struct regmap *regmap, + const u32 *mux_table, + struct muxdiv_cfg *mux_cfg, + struct muxdiv_cfg *div_cfg, + struct gate_cfg *gate_cfg, + unsigned long flags, + spinlock_t *lock) +{ + const struct clk_ops *sky1_gate_ops = NULL; + const struct clk_ops *sky1_mux_ops = NULL; + const struct clk_ops *sky1_div_ops = NULL; + struct sky1_clk_divider *sky1_div = NULL; + struct sky1_clk_gate *sky1_gate = NULL; + struct sky1_clk_mux *sky1_mux = NULL; + struct clk_hw *hw = ERR_PTR(-ENOMEM); + struct clk_parent_data *parent_data; + int i; + + parent_data = devm_kcalloc(dev, num_parents, sizeof(*parent_data), GFP_KERNEL); + if (!parent_data) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < num_parents; i++) + parent_data[i].name = parent_names[i]; + + if (mux_cfg->offset >= 0) { + sky1_mux = devm_kzalloc(dev, sizeof(*sky1_mux), GFP_KERNEL); + if (!sky1_mux) + return ERR_PTR(-ENOMEM); + + sky1_mux->mux.reg = NULL; + sky1_mux->mux.shift = mux_cfg->shift; + sky1_mux->mux.mask = BIT(mux_cfg->width) - 1; + sky1_mux->mux.flags = mux_cfg->flags; + sky1_mux->mux.table = mux_table; + sky1_mux->mux.lock = lock; + sky1_mux_ops = &sky1_audss_clk_mux_ops; + sky1_mux->regmap = regmap; + sky1_mux->offset = mux_cfg->offset; + } + + if (div_cfg->offset >= 0) { + sky1_div = devm_kzalloc(dev, sizeof(*sky1_div), GFP_KERNEL); + if (!sky1_div) + return ERR_PTR(-ENOMEM); + + sky1_div->div.reg = NULL; + sky1_div->div.shift = div_cfg->shift; + sky1_div->div.width = div_cfg->width; + sky1_div->div.flags = div_cfg->flags | CLK_DIVIDER_POWER_OF_TWO; + sky1_div->div.lock = lock; + sky1_div_ops = &sky1_audss_clk_divider_ops; + sky1_div->regmap = regmap; + sky1_div->offset = div_cfg->offset; + } + + if (gate_cfg->offset >= 0) { + sky1_gate = devm_kzalloc(dev, sizeof(*sky1_gate), GFP_KERNEL); + if (!sky1_gate) + return ERR_PTR(-ENOMEM); + + sky1_gate->gate.reg = NULL; + sky1_gate->gate.bit_idx = gate_cfg->shift; + sky1_gate->gate.flags = gate_cfg->flags; + sky1_gate->gate.lock = lock; + sky1_gate_ops = &sky1_audss_clk_gate_ops; + sky1_gate->regmap = regmap; + sky1_gate->offset = gate_cfg->offset; + } + + hw = devm_clk_hw_register_composite_pdata(dev, name, parent_data, num_parents, + sky1_mux ? &sky1_mux->mux.hw : NULL, sky1_mux_ops, + sky1_div ? &sky1_div->div.hw : NULL, sky1_div_ops, + sky1_gate ? &sky1_gate->gate.hw : NULL, sky1_gate_ops, + flags); + if (IS_ERR(hw)) { + dev_err(dev, "register %s clock failed with err = %ld\n", + name, PTR_ERR(hw)); + return hw; + } + + return hw; +} + +static int sky1_audss_clks_get(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i; + + for (i = 0; i < devtype_data->clk_num; i++) { + priv->clks[i] = devm_clk_get(priv->dev, devtype_data->clk_names[i]); + if (IS_ERR(priv->clks[i])) + return dev_err_probe(priv->dev, PTR_ERR(priv->clks[i]), + "failed to get clock %s", devtype_data->clk_names[i]); + } + + return 0; +} + +static int sky1_audss_clks_enable(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i, err; + + for (i = 0; i < devtype_data->clk_num; i++) { + err = clk_prepare_enable(priv->clks[i]); + if (err) { + dev_err(priv->dev, "failed to enable clock %s\n", + devtype_data->clk_names[i]); + goto err_clks; + } + } + + return 0; + +err_clks: + while (--i >= 0) + clk_disable_unprepare(priv->clks[i]); + + return err; +} + +static void sky1_audss_clks_disable(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i; + + for (i = 0; i < devtype_data->clk_num; i++) + clk_disable_unprepare(priv->clks[i]); +} + +static int sky1_audss_clks_set_rate(struct sky1_audss_clks_priv *priv) +{ + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + int i, err; + + for (i = 0; i < devtype_data->clk_num; i++) { + err = clk_set_rate(priv->clks[i], devtype_data->clk_rate_default[i]); + if (err) { + dev_err(priv->dev, "failed to set clock rate %s\n", + devtype_data->clk_names[i]); + return err; + } + } + + return 0; +} + +static void sky1_audss_clk_rpm_cleanup(void *data) +{ + pm_runtime_force_suspend(data); +} + +/* register sky1 audio subsystem clocks */ +static int sky1_audss_clk_probe(struct platform_device *pdev) +{ + const struct sky1_audss_clks_devtype_data *devtype_data; + struct sky1_audss_clks_priv *priv; + struct device *dev = &pdev->dev; + struct clk_hw **clk_table; + void __iomem *base; + int i, ret; + + devtype_data = device_get_match_data(dev); + if (!devtype_data) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + priv->clk_data = devm_kzalloc(dev, + struct_size(priv->clk_data, hws, SKY1_AUDSS_NUM_CLKS), + GFP_KERNEL); + if (!priv->clk_data) + return -ENOMEM; + + priv->clk_data->num = SKY1_AUDSS_NUM_CLKS; + clk_table = priv->clk_data->hws; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap_cru = devm_regmap_init_mmio(dev, base, &sky1_audss_regmap_config); + if (IS_ERR(priv->regmap_cru)) + return dev_err_probe(dev, PTR_ERR(priv->regmap_cru), + "failed to initialize regmap\n"); + + priv->dev = dev; + priv->devtype_data = devtype_data; + + priv->rst_noc = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->rst_noc)) + return dev_err_probe(dev, PTR_ERR(priv->rst_noc), + "failed to get audss noc reset"); + + platform_set_drvdata(pdev, priv); + + reset_control_assert(priv->rst_noc); + + ret = sky1_audss_clks_get(priv); + if (ret) + return ret; + + ret = sky1_audss_clks_enable(priv); + if (ret) + return ret; + + /* release noc reset after clock on */ + ret = reset_control_deassert(priv->rst_noc); + if (ret) { + sky1_audss_clks_disable(priv); + return ret; + } + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* + * Register cleanup action before clocks/provider (released last under LIFO). + */ + ret = devm_add_action_or_reset(dev, sky1_audss_clk_rpm_cleanup, dev); + if (ret) + return ret; + + ret = sky1_audss_clks_set_rate(priv); + if (ret) + goto fail_pm; + + /* audio_clk4 clock fixed divider */ + clk_table[CLK_AUD_CLK4_DIV2] = + devm_clk_hw_register_fixed_factor(dev, + "audio_clk4_div2", + "audio_clk4", + 0, + 1, 2); + if (IS_ERR(clk_table[CLK_AUD_CLK4_DIV2])) { + ret = PTR_ERR(clk_table[CLK_AUD_CLK4_DIV2]); + dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK4_DIV2, ret); + goto fail_pm; + } + + clk_table[CLK_AUD_CLK4_DIV4] = + devm_clk_hw_register_fixed_factor(dev, + "audio_clk4_div4", + "audio_clk4", + 0, + 1, 4); + if (IS_ERR(clk_table[CLK_AUD_CLK4_DIV4])) { + ret = PTR_ERR(clk_table[CLK_AUD_CLK4_DIV4]); + dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK4_DIV4, ret); + goto fail_pm; + } + + /* audio_clk5 clock fixed divider */ + clk_table[CLK_AUD_CLK5_DIV2] = + devm_clk_hw_register_fixed_factor(dev, + "audio_clk5_div2", + "audio_clk5", + 0, + 1, 2); + if (IS_ERR(clk_table[CLK_AUD_CLK5_DIV2])) { + ret = PTR_ERR(clk_table[CLK_AUD_CLK5_DIV2]); + dev_err(dev, "failed to register clock %d, ret:%d\n", CLK_AUD_CLK5_DIV2, ret); + goto fail_pm; + } + + for (i = 0; i < devtype_data->clk_cfg_size; i++) { + clk_table[devtype_data->clk_cfg[i].id] = + sky1_audss_clk_register(dev, + devtype_data->clk_cfg[i].name, + devtype_data->clk_cfg[i].parent_names, + devtype_data->clk_cfg[i].num_parents, + priv->regmap_cru, + devtype_data->clk_cfg[i].mux_table, + devtype_data->clk_cfg[i].mux_cfg, + devtype_data->clk_cfg[i].div_cfg, + devtype_data->clk_cfg[i].gate_cfg, + devtype_data->clk_cfg[i].flags, + &priv->lock); + if (IS_ERR(clk_table[devtype_data->clk_cfg[i].id])) { + ret = PTR_ERR(clk_table[devtype_data->clk_cfg[i].id]); + dev_err(dev, "failed to register clock %d, ret:%d\n", + devtype_data->clk_cfg[i].id, ret); + goto fail_pm; + } + } + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, priv->clk_data); + if (ret) { + dev_err(dev, "failed to add clock provider: %d\n", ret); + goto fail_pm; + } + + ret = sky1_audss_reset_controller_register(dev); + if (ret) { + dev_err(dev, "failed to register reset controller: %d\n", ret); + goto fail_pm; + } + + pm_runtime_put_sync(dev); + + return 0; + +fail_pm: + pm_runtime_put_sync(dev); + return ret; +} + +static int __maybe_unused sky1_audss_clk_runtime_suspend(struct device *dev) +{ + struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev); + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + unsigned long flags; + int i; + + spin_lock_irqsave(&priv->lock, flags); + for (i = 0; i < devtype_data->reg_save_size; i++) + regmap_read(priv->regmap_cru, + devtype_data->reg_save[i][0], &devtype_data->reg_save[i][1]); + spin_unlock_irqrestore(&priv->lock, flags); + + reset_control_assert(priv->rst_noc); + + sky1_audss_clks_disable(priv); + + return 0; +} + +static int __maybe_unused sky1_audss_clk_runtime_resume(struct device *dev) +{ + struct sky1_audss_clks_priv *priv = dev_get_drvdata(dev); + const struct sky1_audss_clks_devtype_data *devtype_data = priv->devtype_data; + unsigned long flags; + int i, ret; + + ret = sky1_audss_clks_enable(priv); + if (ret) { + dev_err(dev, "failed to enable clocks\n"); + return ret; + } + + ret = reset_control_deassert(priv->rst_noc); + if (ret) { + sky1_audss_clks_disable(priv); + return ret; + } + + spin_lock_irqsave(&priv->lock, flags); + for (i = 0; i < devtype_data->reg_save_size; i++) + regmap_write(priv->regmap_cru, + devtype_data->reg_save[i][0], devtype_data->reg_save[i][1]); + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static const struct dev_pm_ops sky1_audss_clk_pm_ops = { + SET_RUNTIME_PM_OPS(sky1_audss_clk_runtime_suspend, + sky1_audss_clk_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct sky1_audss_clks_devtype_data sky1_devtype_data = { + .reg_save = sky1_reg_save, + .reg_save_size = ARRAY_SIZE(sky1_reg_save), + .clk_names = sky1_audss_clk_names, + .clk_num = ARRAY_SIZE(sky1_audss_clk_names), + .clk_rate_default = sky1_clk_rate_default, + .clk_cfg = sky1_audss_clks, + .clk_cfg_size = ARRAY_SIZE(sky1_audss_clks), +}; + +static const struct of_device_id sky1_audss_clk_of_match[] = { + { .compatible = "cix,sky1-audss-cru", .data = &sky1_devtype_data, }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sky1_audss_clk_of_match); + +static struct platform_driver sky1_audss_clk_driver = { + .probe = sky1_audss_clk_probe, + .driver = { + .name = "sky1-audss-clk", + .suppress_bind_attrs = true, + .of_match_table = sky1_audss_clk_of_match, + .pm = pm_ptr(&sky1_audss_clk_pm_ops), + }, +}; +module_platform_driver(sky1_audss_clk_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cixtech Sky1 Audio Subsystem Clock Controller Driver"); From b5fa9d3beec483d125757127b804470941e191af Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:07 +0800 Subject: [PATCH 26/45] reset: cix: add sky1 audss auxiliary reset driver Add an auxiliary reset controller driver for the AUDSS CRU. Sixteen software reset lines for audio subsystem peripherals are controlled through one register in the CRU register map. The driver is created by the AUDSS clock platform driver and registers the reset controller on the CRU device node. Signed-off-by: Joakim Zhang Reviewed-by: Philipp Zabel Acked-by: Philipp Zabel Signed-off-by: Brian Masney --- drivers/reset/Kconfig | 13 ++++ drivers/reset/Makefile | 1 + drivers/reset/reset-sky1-audss.c | 127 +++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/reset/reset-sky1-audss.c diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index d009eb0849a3..b19e719f2abe 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -300,6 +300,19 @@ config RESET_SKY1 help This enables the reset controller for Cix Sky1. +config RESET_SKY1_AUDSS + tristate "Cix Sky1 Audio Subsystem reset controller" + depends on ARCH_CIX || COMPILE_TEST + select AUXILIARY_BUS + default CLK_SKY1_AUDSS + help + Support for block-level software reset lines in the Cix Sky1 + Audio Subsystem (AUDSS) Clock and Reset Unit. Sixteen reset + outputs for audio peripherals are controlled through the CRU + register map. The driver binds as an auxiliary device from + the AUDSS clock driver. Say M or Y here if you want to build + this driver. + config RESET_SOCFPGA bool "SoCFPGA Reset Driver" if COMPILE_TEST && (!ARM || !ARCH_INTEL_SOCFPGA) default ARM && ARCH_INTEL_SOCFPGA diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 3e52569bd276..e81407ea3e29 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_RESET_RZV2H_USB2PHY) += reset-rzv2h-usb2phy.o obj-$(CONFIG_RESET_SCMI) += reset-scmi.o obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o obj-$(CONFIG_RESET_SKY1) += reset-sky1.o +obj-$(CONFIG_RESET_SKY1_AUDSS) += reset-sky1-audss.o obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o diff --git a/drivers/reset/reset-sky1-audss.c b/drivers/reset/reset-sky1-audss.c new file mode 100644 index 000000000000..d9735db681c7 --- /dev/null +++ b/drivers/reset/reset-sky1-audss.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Cix Sky1 Audio Subsystem reset controller driver + * + * Copyright 2026 Cix Technology Group Co., Ltd. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define SKY1_RESET_SLEEP_US 50 + +#define AUDSS_SW_RST 0x78 + +struct sky1_audss_reset_map { + unsigned int offset; + unsigned int mask; +}; + +struct sky1_audss_reset { + struct reset_controller_dev rcdev; + struct regmap *regmap; + const struct sky1_audss_reset_map *map; +}; + +static const struct sky1_audss_reset_map sky1_audss_reset_map[] = { + [AUDSS_I2S0_SW_RST] = { AUDSS_SW_RST, BIT(0) }, + [AUDSS_I2S1_SW_RST] = { AUDSS_SW_RST, BIT(1) }, + [AUDSS_I2S2_SW_RST] = { AUDSS_SW_RST, BIT(2) }, + [AUDSS_I2S3_SW_RST] = { AUDSS_SW_RST, BIT(3) }, + [AUDSS_I2S4_SW_RST] = { AUDSS_SW_RST, BIT(4) }, + [AUDSS_I2S5_SW_RST] = { AUDSS_SW_RST, BIT(5) }, + [AUDSS_I2S6_SW_RST] = { AUDSS_SW_RST, BIT(6) }, + [AUDSS_I2S7_SW_RST] = { AUDSS_SW_RST, BIT(7) }, + [AUDSS_I2S8_SW_RST] = { AUDSS_SW_RST, BIT(8) }, + [AUDSS_I2S9_SW_RST] = { AUDSS_SW_RST, BIT(9) }, + [AUDSS_WDT_SW_RST] = { AUDSS_SW_RST, BIT(10) }, + [AUDSS_TIMER_SW_RST] = { AUDSS_SW_RST, BIT(11) }, + [AUDSS_MB0_SW_RST] = { AUDSS_SW_RST, BIT(12) }, + [AUDSS_MB1_SW_RST] = { AUDSS_SW_RST, BIT(13) }, + [AUDSS_HDA_SW_RST] = { AUDSS_SW_RST, BIT(14) }, + [AUDSS_DMAC_SW_RST] = { AUDSS_SW_RST, BIT(15) }, +}; + +static struct sky1_audss_reset *to_sky1_audss_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct sky1_audss_reset, rcdev); +} + +static int sky1_audss_reset_set(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct sky1_audss_reset *priv = to_sky1_audss_reset(rcdev); + const struct sky1_audss_reset_map *signal = &priv->map[id]; + int ret; + + ret = regmap_assign_bits(priv->regmap, signal->offset, + signal->mask, !assert); + if (ret) + return ret; + + fsleep(SKY1_RESET_SLEEP_US); + return 0; +} + +static int sky1_audss_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sky1_audss_reset_set(rcdev, id, true); +} + +static int sky1_audss_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sky1_audss_reset_set(rcdev, id, false); +} + +static const struct reset_control_ops sky1_audss_reset_ops = { + .assert = sky1_audss_reset_assert, + .deassert = sky1_audss_reset_deassert, +}; + +static int sky1_audss_reset_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct sky1_audss_reset *priv; + struct device *dev = &adev->dev; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = dev_get_regmap(dev->parent, NULL); + if (!priv->regmap) + return dev_err_probe(dev, -ENODEV, "failed to get parent regmap\n"); + + priv->map = sky1_audss_reset_map; + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.nr_resets = ARRAY_SIZE(sky1_audss_reset_map); + priv->rcdev.ops = &sky1_audss_reset_ops; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.dev = dev; + + return devm_reset_controller_register(dev, &priv->rcdev); +} + +static const struct auxiliary_device_id sky1_audss_reset_ids[] = { + { .name = "clk_sky1_audss.reset" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, sky1_audss_reset_ids); + +static struct auxiliary_driver sky1_audss_reset_driver = { + .probe = sky1_audss_reset_probe, + .id_table = sky1_audss_reset_ids, +}; +module_auxiliary_driver(sky1_audss_reset_driver); + +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cix Sky1 Audio Subsystem reset driver"); +MODULE_LICENSE("GPL"); From a2b1c025cf288473d9604be0b75d3b3362dac113 Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Thu, 23 Jul 2026 17:08:08 +0800 Subject: [PATCH 27/45] arm64: dts: cix: sky1: add audss cru Add the AUDSS CRU device node providing clocks and software resets for audio subsystem peripherals. Signed-off-by: Joakim Zhang Signed-off-by: Brian Masney --- arch/arm64/boot/dts/cix/sky1.dtsi | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/arm64/boot/dts/cix/sky1.dtsi b/arch/arm64/boot/dts/cix/sky1.dtsi index a77c52296ebd..9b2d969f6bfe 100644 --- a/arch/arm64/boot/dts/cix/sky1.dtsi +++ b/arch/arm64/boot/dts/cix/sky1.dtsi @@ -6,6 +6,10 @@ #include #include +#include +#include +#include +#include #include "sky1-power.h" / { @@ -558,6 +562,20 @@ mbox_pm2ap: mailbox@65a0080 { cix,mbox-dir = "rx"; }; + audss_cru: clock-controller@7110000 { + compatible = "cix,sky1-audss-cru"; + reg = <0x0 0x07110000 0x0 0x10000>; + #clock-cells = <1>; + #reset-cells = <1>; + clocks = <&scmi_clk CLK_TREE_AUDIO_CLK0>, + <&scmi_clk CLK_TREE_AUDIO_CLK2>, + <&scmi_clk CLK_TREE_AUDIO_CLK4>, + <&scmi_clk CLK_TREE_AUDIO_CLK5>; + clock-names = "x8k", "x11k", "sys", "48m"; + power-domains = <&smc_devpd SKY1_PD_AUDIO>; + resets = <&s5_syscon SKY1_AUDIO_HIFI5_NOC_RESET_N>; + }; + mbox_sfh2ap: mailbox@8090000 { compatible = "cix,sky1-mbox"; reg = <0x0 0x08090000 0x0 0x10000>; From e6850b69b674604d6d6dec980655e5e45a177c9b Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 23 Jul 2026 21:01:30 +0200 Subject: [PATCH 28/45] dt-bindings: clock: airoha: Add additional reset for PCIe PERSTOUT Add additional reset to control PCIe PERSTOUT reset line for each of the 3 PCIe lines. Signed-off-by: Christian Marangi Acked-by: Krzysztof Kozlowski Signed-off-by: Brian Masney --- include/dt-bindings/reset/airoha,en7581-reset.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/dt-bindings/reset/airoha,en7581-reset.h b/include/dt-bindings/reset/airoha,en7581-reset.h index 6544a1790b83..25e75534daa9 100644 --- a/include/dt-bindings/reset/airoha,en7581-reset.h +++ b/include/dt-bindings/reset/airoha,en7581-reset.h @@ -62,5 +62,9 @@ #define EN7581_CPU_TIMER_RST 50 #define EN7581_PCIE_HB_RST 51 #define EN7581_XPON_MAC_RST 52 +/* RST_PCIC */ +#define EN7581_PCIC_PERSTOUT0_RST 53 +#define EN7581_PCIC_PERSTOUT1_RST 54 +#define EN7581_PCIC_PERSTOUT2_RST 55 #endif /* __DT_BINDINGS_RESET_CONTROLLER_AIROHA_EN7581_H_ */ From 6712f48eb3a15842925171441505fa2e7699c57e Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 23 Jul 2026 21:01:31 +0200 Subject: [PATCH 29/45] clk: en7523: add support for dedicated PCIe PERSTOUT reset Add support for resetting the PCIe lines with the PERSTOUT reset. These special reset are controlled by the PCIC register and are specific to each of the 3 PCIe lines. Contrary to the dedicated reset register where 0 deassert and 1 assert the reset line for the related bit, for PCIe PERSTOUT reset in the PCIC register, the deassert and assert value is swapped, with 1 for deassert and 0 for assert. (HW have these bit set as 1 by default after a cold boot) This is internally handled in the reset function. PCI enable/disable are updated to drop PERSTOUT bits in favor dedicated reset handling. Signed-off-by: Christian Marangi Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-en7523.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/drivers/clk/clk-en7523.c b/drivers/clk/clk-en7523.c index c450d89bfa96..5c45aacb6409 100644 --- a/drivers/clk/clk-en7523.c +++ b/drivers/clk/clk-en7523.c @@ -339,6 +339,7 @@ static const struct en_clk_desc en7581_base_clks[] = { static const u16 en7581_rst_ofs[] = { REG_RST_CTRL2, REG_RST_CTRL1, + REG_NP_SCU_PCIC, }; static const u16 en751221_rst_ofs[] = { @@ -451,6 +452,11 @@ static const u16 en7581_rst_map[] = { [EN7581_CPU_TIMER_RST] = RST_NR_PER_BANK + 28, [EN7581_PCIE_HB_RST] = RST_NR_PER_BANK + 29, [EN7581_XPON_MAC_RST] = RST_NR_PER_BANK + 31, + + /* RST_PCIC */ + [EN7581_PCIC_PERSTOUT0_RST] = 2 * RST_NR_PER_BANK + 29, + [EN7581_PCIC_PERSTOUT1_RST] = 2 * RST_NR_PER_BANK + 26, + [EN7581_PCIC_PERSTOUT2_RST] = 2 * RST_NR_PER_BANK + 16, }; static const u16 en751221_rst_map[] = { @@ -636,9 +642,7 @@ static int en7581_pci_enable(struct clk_hw *hw) void __iomem *np_base = cg->base; u32 val, mask; - mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1 | - REG_PCI_CONTROL_PERSTOUT1 | REG_PCI_CONTROL_PERSTOUT2 | - REG_PCI_CONTROL_PERSTOUT; + mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1; val = readl(np_base + REG_PCI_CONTROL); writel(val | mask, np_base + REG_PCI_CONTROL); @@ -651,9 +655,7 @@ static void en7581_pci_disable(struct clk_hw *hw) void __iomem *np_base = cg->base; u32 val, mask; - mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1 | - REG_PCI_CONTROL_PERSTOUT1 | REG_PCI_CONTROL_PERSTOUT2 | - REG_PCI_CONTROL_PERSTOUT; + mask = REG_PCI_CONTROL_REFCLK_EN0 | REG_PCI_CONTROL_REFCLK_EN1; val = readl(np_base + REG_PCI_CONTROL); writel(val & ~mask, np_base + REG_PCI_CONTROL); usleep_range(1000, 2000); @@ -755,11 +757,17 @@ static int en7523_reset_update(struct reset_controller_dev *rcdev, unsigned long id, bool assert) { struct en_rst_data *rst_data = container_of(rcdev, struct en_rst_data, rcdev); - void __iomem *addr = rst_data->base + rst_data->bank_ofs[id / RST_NR_PER_BANK]; + u32 offset = rst_data->bank_ofs[id / RST_NR_PER_BANK]; + void __iomem *addr = rst_data->base + offset; + bool inverted = false; u32 val; + /* For PCIC reset logic is inverted, 0:assert 1:deassert */ + if (offset == REG_NP_SCU_PCIC) + inverted = true; + val = readl(addr); - if (assert) + if (assert ^ inverted) val |= BIT(id % RST_NR_PER_BANK); else val &= ~BIT(id % RST_NR_PER_BANK); @@ -784,9 +792,17 @@ static int en7523_reset_status(struct reset_controller_dev *rcdev, unsigned long id) { struct en_rst_data *rst_data = container_of(rcdev, struct en_rst_data, rcdev); - void __iomem *addr = rst_data->base + rst_data->bank_ofs[id / RST_NR_PER_BANK]; + u32 offset = rst_data->bank_ofs[id / RST_NR_PER_BANK]; + void __iomem *addr = rst_data->base + offset; + bool inverted = false; + u32 val; - return !!(readl(addr) & BIT(id % RST_NR_PER_BANK)); + /* For PCIC reset logic is inverted, 0:assert 1:deassert */ + if (offset == REG_NP_SCU_PCIC) + inverted = true; + + val = readl(addr) & BIT(id % RST_NR_PER_BANK); + return inverted ? !val : !!val; } static int en7523_reset_xlate(struct reset_controller_dev *rcdev, From b698927accb1c974040dc6596c8df1d72682b2ed Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Thu, 23 Jul 2026 18:08:32 +0530 Subject: [PATCH 30/45] clk: Add devm_clk_bulk_get_enable() devm_clk_bulk_get_optional_enable() gets, prepares and enables a set of clocks with device-managed cleanup, but treats every clock as optional: a missing clock is silently returned as NULL instead of failing. Consumers that need a fixed set of mandatory clocks enabled for the lifetime of the device currently have to open-code devm_clk_bulk_get() followed by clk_bulk_prepare_enable(), which loses the managed disable on unbind, or fall back to per-clock devm_clk_get_enabled() calls. Add devm_clk_bulk_get_enable() as the non-optional counterpart. The underlying __devm_clk_bulk_get_enable() helper already supports the required (optional = false) path, so only export a thin wrapper for it. Signed-off-by: Suraj Gupta Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- .../driver-api/driver-model/devres.rst | 1 + drivers/clk/clk-devres.c | 7 ++++++ include/linux/clk.h | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/Documentation/driver-api/driver-model/devres.rst b/Documentation/driver-api/driver-model/devres.rst index 017fb155a5bc..5067500ded5c 100644 --- a/Documentation/driver-api/driver-model/devres.rst +++ b/Documentation/driver-api/driver-model/devres.rst @@ -248,6 +248,7 @@ CLOCK devm_clk_put() devm_clk_bulk_get() devm_clk_bulk_get_all() + devm_clk_bulk_get_enable() devm_clk_bulk_get_optional() devm_get_clk_from_child() devm_clk_hw_register() diff --git a/drivers/clk/clk-devres.c b/drivers/clk/clk-devres.c index 994d5bc5168b..49d4782991eb 100644 --- a/drivers/clk/clk-devres.c +++ b/drivers/clk/clk-devres.c @@ -222,6 +222,13 @@ static int __devm_clk_bulk_get_enable(struct device *dev, int num_clks, return ret; } +int __must_check devm_clk_bulk_get_enable(struct device *dev, int num_clks, + struct clk_bulk_data *clks) +{ + return __devm_clk_bulk_get_enable(dev, num_clks, clks, false); +} +EXPORT_SYMBOL_GPL(devm_clk_bulk_get_enable); + int __must_check devm_clk_bulk_get_optional_enable(struct device *dev, int num_clks, struct clk_bulk_data *clks) { diff --git a/include/linux/clk.h b/include/linux/clk.h index 993fd6e916c7..db9cf184fd99 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -502,6 +502,22 @@ int __must_check devm_clk_bulk_get(struct device *dev, int num_clks, */ int __must_check devm_clk_bulk_get_optional(struct device *dev, int num_clks, struct clk_bulk_data *clks); +/** + * devm_clk_bulk_get_enable - Get and enable bulk clocks (managed) + * @dev: device for clock "consumer" + * @num_clks: the number of clk_bulk_data + * @clks: pointer to the clk_bulk_data table of consumer + * + * Behaves the same as devm_clk_bulk_get() but also prepares and enables the + * clocks in one operation with management. The clks will automatically be + * disabled, unprepared and freed when the device is unbound. + * + * Return: 0 if all clocks specified in clk_bulk_data table are obtained and + * enabled successfully. Otherwise returns valid IS_ERR() condition containing + * errno. + */ +int __must_check devm_clk_bulk_get_enable(struct device *dev, int num_clks, + struct clk_bulk_data *clks); /** * devm_clk_bulk_get_optional_enable - Get and enable optional bulk clocks (managed) * @dev: device for clock "consumer" @@ -1052,6 +1068,13 @@ static inline int __must_check devm_clk_bulk_get_optional(struct device *dev, return 0; } +static inline int __must_check devm_clk_bulk_get_enable(struct device *dev, + int num_clks, + struct clk_bulk_data *clks) +{ + return 0; +} + static inline int __must_check devm_clk_bulk_get_optional_enable(struct device *dev, int num_clks, struct clk_bulk_data *clks) From 4e324f1bc00390508444256b01e8568b5ddbdae7 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 24 Jul 2026 03:45:19 +0900 Subject: [PATCH 31/45] clk: remove conditional return with no effect Both branches of the check return the same value, so the check has no effect. Remove it and return the value directly. In the lmk04832 driver, the hardware sequence comments are moved above the final return by hand. This is the result of running the Coccinelle script from scripts/coccinelle/misc/cond_return_no_effect.cocci. Signed-off-by: Sang-Heon Jeon Reviewed-by: Brian Masney Signed-off-by: Brian Masney --- drivers/clk/clk-cs2000-cp.c | 6 +----- drivers/clk/clk-lmk04832.c | 12 ++++-------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/clk/clk-cs2000-cp.c b/drivers/clk/clk-cs2000-cp.c index 8800472ba63f..4de581771d7d 100644 --- a/drivers/clk/clk-cs2000-cp.c +++ b/drivers/clk/clk-cs2000-cp.c @@ -404,11 +404,7 @@ static int cs2000_enable(struct clk_hw *hw) if (ret < 0) return ret; - ret = cs2000_wait_pll_lock(priv); - if (ret < 0) - return ret; - - return ret; + return cs2000_wait_pll_lock(priv); } static void cs2000_disable(struct clk_hw *hw) diff --git a/drivers/clk/clk-lmk04832.c b/drivers/clk/clk-lmk04832.c index 9bf86caad829..f006401c1586 100644 --- a/drivers/clk/clk-lmk04832.c +++ b/drivers/clk/clk-lmk04832.c @@ -822,13 +822,6 @@ static int lmk04832_sclk_sync_sequence(struct lmk04832 *lmk) if (ret) return ret; - ret = regmap_update_bits(lmk->regmap, LMK04832_REG_SYNC, - LMK04832_BIT_SYNC_MODE, - FIELD_PREP(LMK04832_BIT_SYNC_MODE, - lmk->sync_mode)); - if (ret) - return ret; - /* * 9. (optional) if SCLKx_y_DIS_MODE was used to mute SYSREF outputs * during the SYNC event, restore SCLKx_y_DIS_MODE=0 for active state, @@ -843,7 +836,10 @@ static int lmk04832_sclk_sync_sequence(struct lmk04832 *lmk) * SYNC pulse to delay the output by some number of VCO counts). */ - return ret; + return regmap_update_bits(lmk->regmap, LMK04832_REG_SYNC, + LMK04832_BIT_SYNC_MODE, + FIELD_PREP(LMK04832_BIT_SYNC_MODE, + lmk->sync_mode)); } static int lmk04832_sclk_is_enabled(struct clk_hw *hw) From ef6ca9c4df02753fac81d70ae4c3f02bf784a4f0 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 24 Jul 2026 11:03:52 +0200 Subject: [PATCH 32/45] clk: mvebu: clean-up simple provider misuse of the consumer API Clock provider should not be using the consumer interface. In other words, a provider should not be dealing with struct clk. This change targets occurrences for which the provider uses the consumer interface and corresponding clk_hw interface exist. Reviewed-by: Brian Masney Signed-off-by: Jerome Brunet Reviewed-by: Thierry Reding Signed-off-by: Brian Masney --- drivers/clk/mvebu/clk-cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/mvebu/clk-cpu.c b/drivers/clk/mvebu/clk-cpu.c index 26d52ecaac50..24196e288fe2 100644 --- a/drivers/clk/mvebu/clk-cpu.c +++ b/drivers/clk/mvebu/clk-cpu.c @@ -153,7 +153,7 @@ static int clk_cpu_on_set_rate(struct clk_hw *hwclk, unsigned long rate, static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate, unsigned long parent_rate) { - if (__clk_is_enabled(hwclk->clk)) + if (clk_hw_is_enabled(hwclk)) return clk_cpu_on_set_rate(hwclk, rate, parent_rate); else return clk_cpu_off_set_rate(hwclk, rate, parent_rate); From 227d023952058976bf336a36079d49b8a50c9b02 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 24 Jul 2026 11:03:56 +0200 Subject: [PATCH 33/45] clk: st: clean-up simple provider misuse of the consumer API Clock provider should not be using the consumer interface. In other words, a provider should not be dealing with struct clk. This change targets occurrences for which the provider uses the consumer interface and corresponding clk_hw interface exist. Reviewed-by: Brian Masney Signed-off-by: Jerome Brunet Reviewed-by: Thierry Reding Signed-off-by: Brian Masney --- drivers/clk/st/clkgen-pll.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/clk/st/clkgen-pll.c b/drivers/clk/st/clkgen-pll.c index 9eb2cb83fa69..17551c6a879a 100644 --- a/drivers/clk/st/clkgen-pll.c +++ b/drivers/clk/st/clkgen-pll.c @@ -260,7 +260,7 @@ static int __clkgen_pll_enable(struct clk_hw *hw) if (pll->data->switch2pll_en) CLKGEN_WRITE(pll, switch2pll, 0); - pr_debug("%s:%s enabled\n", __clk_get_name(hw->clk), __func__); + pr_debug("%s:%s enabled\n", clk_hw_get_name(hw), __func__); } return ret; @@ -295,7 +295,7 @@ static void __clkgen_pll_disable(struct clk_hw *hw) CLKGEN_WRITE(pll, pdn_ctrl, 1); - pr_debug("%s:%s disabled\n", __clk_get_name(hw->clk), __func__); + pr_debug("%s:%s disabled\n", clk_hw_get_name(hw), __func__); } static void clkgen_pll_disable(struct clk_hw *hw) @@ -405,14 +405,14 @@ static int stm_pll3200c32_determine_rate(struct clk_hw *hw, &req->rate); else { pr_debug("%s: %s rate %ld Invalid\n", __func__, - __clk_get_name(hw->clk), req->rate); + clk_hw_get_name(hw), req->rate); req->rate = 0; return 0; } pr_debug("%s: %s new rate %ld [ndiv=%u] [idf=%u]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), req->rate, (unsigned int)params.ndiv, (unsigned int)params.idf); @@ -434,7 +434,7 @@ static int set_rate_stm_pll3200c32(struct clk_hw *hw, unsigned long rate, clk_pll3200c32_get_rate(parent_rate, ¶ms, &hwrate); pr_debug("%s: %s new rate %ld [ndiv=0x%x] [idf=0x%x]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), hwrate, (unsigned int)params.ndiv, (unsigned int)params.idf); @@ -547,7 +547,7 @@ static unsigned long recalc_stm_pll4600c28(struct clk_hw *hw, clk_pll4600c28_get_rate(parent_rate, ¶ms, &rate); - pr_debug("%s:%s rate %lu\n", __clk_get_name(hw->clk), __func__, rate); + pr_debug("%s:%s rate %lu\n", clk_hw_get_name(hw), __func__, rate); return rate; } @@ -562,14 +562,14 @@ static int stm_pll4600c28_determine_rate(struct clk_hw *hw, &req->rate); } else { pr_debug("%s: %s rate %ld Invalid\n", __func__, - __clk_get_name(hw->clk), req->rate); + clk_hw_get_name(hw), req->rate); req->rate = 0; return 0; } pr_debug("%s: %s new rate %ld [ndiv=%u] [idf=%u]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), req->rate, (unsigned int)params.ndiv, (unsigned int)params.idf); @@ -591,12 +591,12 @@ static int set_rate_stm_pll4600c28(struct clk_hw *hw, unsigned long rate, clk_pll4600c28_get_rate(parent_rate, ¶ms, &hwrate); } else { pr_debug("%s: %s rate %ld Invalid\n", __func__, - __clk_get_name(hw->clk), rate); + clk_hw_get_name(hw), rate); return -EINVAL; } pr_debug("%s: %s new rate %ld [ndiv=0x%x] [idf=0x%x]\n", - __func__, __clk_get_name(hw->clk), + __func__, clk_hw_get_name(hw), hwrate, (unsigned int)params.ndiv, (unsigned int)params.idf); From 3ed463b69be8120d8f674051f0fe3304ef9839cc Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Fri, 24 Jul 2026 11:03:57 +0200 Subject: [PATCH 34/45] clk: tegra: clean-up simple provider misuse of the consumer API Clock provider should not be using the consumer interface. In other words, a provider should not be dealing with struct clk. This change targets occurrences for which the provider uses the consumer interface and corresponding clk_hw interface exist. Reviewed-by: Brian Masney Signed-off-by: Jerome Brunet Reviewed-by: Thierry Reding Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-tegra210.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index 0c86cff719e3..df1d7b471745 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -1460,7 +1460,7 @@ static int tegra210_pllx_dyn_ramp(struct tegra_clk_pll *pllx, udelay(1); pr_debug("%s: dynamic ramp to m = %u n = %u p = %u, Fout = %lu kHz\n", - __clk_get_name(pllx->hw.clk), cfg->m, cfg->n, cfg->p, + clk_hw_get_name(&pllx->hw), cfg->m, cfg->n, cfg->p, cfg->input_rate / cfg->m * cfg->n / pllx->params->pdiv_tohw[cfg->p].pdiv / 1000); From 442d24ce7f4c09a7d14b949d7a6b640bd63cc4e0 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 28 Jul 2026 17:17:59 +0800 Subject: [PATCH 35/45] dt-bindings: clock: mediatek: Add mt8173 mfgtop The MFG (GPU) block on the MT8173 has a small glue layer, named MFG_TOP in the datasheet, that contains clock gates, some power sequence signal delays, and other unknown registers that get toggled when the GPU is powered on. The clock gates are exposed as clocks provided by a clock controller, while the power sequencing bits are exposed as one singular power domain. Reviewed-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chen-Yu Tsai Signed-off-by: Brian Masney --- .../clock/mediatek,mt8173-mfgtop.yaml | 70 +++++++++++++++++++ include/dt-bindings/clock/mt8173-clk.h | 7 ++ 2 files changed, 77 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml diff --git a/Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml b/Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml new file mode 100644 index 000000000000..03db1ee9e594 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/mediatek,mt8173-mfgtop.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- + +$id: http://devicetree.org/schemas/clock/mediatek,mt8173-mfgtop.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT8173 MFG TOP controller + +maintainers: + - AngeloGioacchino Del Regno + +description: + The MFG TOP glue layer controls various signals going to the MFG (GPU) + block on the MT8173. + +properties: + compatible: + const: mediatek,mt8173-mfgtop + + reg: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: sys + - const: mem + - const: core + - const: clk26m + + power-domains: + maxItems: 1 + + '#clock-cells': + const: 1 + + '#power-domain-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - power-domains + - '#clock-cells' + - '#power-domain-cells' + +additionalProperties: false + +examples: + - | + #include + #include + + clock-controller@13fff000 { + compatible = "mediatek,mt8173-mfgtop"; + reg = <0x13fff000 0x1000>; + clocks = <&topckgen CLK_TOP_AXI_MFG_IN_SEL>, + <&topckgen CLK_TOP_MEM_MFG_IN_SEL>, + <&topckgen CLK_TOP_MFG_SEL>, + <&clk26m>; + clock-names = "sys", "mem", "core", "clk26m"; + power-domains = <&spm MT8173_POWER_DOMAIN_MFG>; + #clock-cells = <1>; + #power-domain-cells = <0>; + }; diff --git a/include/dt-bindings/clock/mt8173-clk.h b/include/dt-bindings/clock/mt8173-clk.h index 3d00c98b9654..89e982f771db 100644 --- a/include/dt-bindings/clock/mt8173-clk.h +++ b/include/dt-bindings/clock/mt8173-clk.h @@ -243,6 +243,13 @@ #define CLK_IMG_FD 7 #define CLK_IMG_NR_CLK 8 +/* MFG_SYS */ + +#define CLK_MFG_AXI 0 +#define CLK_MFG_MEM 1 +#define CLK_MFG_G3D 2 +#define CLK_MFG_26M 3 + /* MM_SYS */ #define CLK_MM_SMI_COMMON 1 From ebd0b73d2137c2277440da04124a0ce7011b2d1a Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 28 Jul 2026 17:18:00 +0800 Subject: [PATCH 36/45] clk: mediatek: Add mt8173-mfgtop driver The MFG (GPU) block on the MT8173 has a small glue layer, named MFG_TOP in the datasheet, that contains clock gates, some power sequence signal delays, and other unknown registers that get toggled when the GPU is powered on. The clock gates are exposed as clocks provided by a clock controller, while the power sequencing bits are exposed as one singular power domain. Tested-by: Icenowy Zheng Reviewed-by: Brian Masney Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Chen-Yu Tsai Signed-off-by: Brian Masney --- drivers/clk/mediatek/Kconfig | 10 + drivers/clk/mediatek/Makefile | 1 + drivers/clk/mediatek/clk-mt8173-mfgtop.c | 242 +++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 drivers/clk/mediatek/clk-mt8173-mfgtop.c diff --git a/drivers/clk/mediatek/Kconfig b/drivers/clk/mediatek/Kconfig index 2c09fd729bab..8eea3766a910 100644 --- a/drivers/clk/mediatek/Kconfig +++ b/drivers/clk/mediatek/Kconfig @@ -537,6 +537,16 @@ config COMMON_CLK_MT8173_IMGSYS help This driver supports MediaTek MT8173 imgsys clocks. +config COMMON_CLK_MT8173_MFGTOP + tristate "Clock and power driver for MediaTek MT8173 mfgtop" + depends on COMMON_CLK_MT8173 + depends on PM + default COMMON_CLK_MT8173 + select PM_GENERIC_DOMAINS + select PM_GENERIC_DOMAINS_OF + help + This driver supports MediaTek MT8173 mfgtop clocks and power domain. + config COMMON_CLK_MT8173_MMSYS tristate "Clock driver for MediaTek MT8173 mmsys" depends on COMMON_CLK_MT8173 diff --git a/drivers/clk/mediatek/Makefile b/drivers/clk/mediatek/Makefile index d8736a060dbd..892a54eeb281 100644 --- a/drivers/clk/mediatek/Makefile +++ b/drivers/clk/mediatek/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_COMMON_CLK_MT8167_VDECSYS) += clk-mt8167-vdec.o obj-$(CONFIG_COMMON_CLK_MT8173) += clk-mt8173-apmixedsys.o clk-mt8173-infracfg.o \ clk-mt8173-pericfg.o clk-mt8173-topckgen.o obj-$(CONFIG_COMMON_CLK_MT8173_IMGSYS) += clk-mt8173-img.o +obj-$(CONFIG_COMMON_CLK_MT8173_MFGTOP) += clk-mt8173-mfgtop.o obj-$(CONFIG_COMMON_CLK_MT8173_MMSYS) += clk-mt8173-mm.o obj-$(CONFIG_COMMON_CLK_MT8173_VDECSYS) += clk-mt8173-vdecsys.o obj-$(CONFIG_COMMON_CLK_MT8173_VENCSYS) += clk-mt8173-vencsys.o diff --git a/drivers/clk/mediatek/clk-mt8173-mfgtop.c b/drivers/clk/mediatek/clk-mt8173-mfgtop.c new file mode 100644 index 000000000000..5669ca9954dc --- /dev/null +++ b/drivers/clk/mediatek/clk-mt8173-mfgtop.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024 Google LLC + * Author: Chen-Yu Tsai + * + * Based on driver in downstream ChromeOS v5.15 kernel. + * + * Copyright (c) 2014 MediaTek Inc. + * Author: Chiawen Lee + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clk-gate.h" +#include "clk-mtk.h" + +static const struct mtk_gate_regs mfg_cg_regs = { + .sta_ofs = 0x0000, + .clr_ofs = 0x0008, + .set_ofs = 0x0004, +}; + +#define GATE_MFG(_id, _name, _parent, _shift, _flags) \ + GATE_MTK_FLAGS(_id, _name, _parent, &mfg_cg_regs, _shift, &mtk_clk_gate_ops_setclr, _flags) + +/* TODO: The block actually has dividers for the core and mem clocks. */ +static const struct mtk_gate mfg_clks[] = { + GATE_MFG(CLK_MFG_AXI, "mfg_axi", "axi_mfg_in_sel", 0, CLK_SET_RATE_PARENT), + GATE_MFG(CLK_MFG_MEM, "mfg_mem", "mem_mfg_in_sel", 1, CLK_SET_RATE_PARENT), + GATE_MFG(CLK_MFG_G3D, "mfg_g3d", "mfg_sel", 2, CLK_SET_RATE_PARENT), + GATE_MFG(CLK_MFG_26M, "mfg_26m", "clk26m", 3, 0), +}; + +struct mt8173_mfgtop_data { + struct clk_hw_onecell_data *clk_data; + struct regmap *regmap; + struct generic_pm_domain genpd; + struct of_phandle_args parent_pd, child_pd; + struct clk *clk_26m; +}; + +/* Delay count in clock cycles */ +#define MFG_ACTIVE_POWER_CON0 0x24 + #define RST_B_DELAY_CNT GENMASK(7, 0) /* pwr_rst_b de-assert delay during power-up */ + #define CLK_EN_DELAY_CNT GENMASK(15, 8) /* CLK_DIS deassert delay during power-up */ + #define CLK_DIS_DELAY_CNT GENMASK(23, 16) /* CLK_DIS assert delay during power-down */ + #define FORCE_ABORT BIT(30) /* write 1 to force abort a power event */ + #define ACTIVE_PWRCTL_EN BIT(31) /* enable ACTIVE_POWER */ + +#define MFG_ACTIVE_POWER_CON1 0x28 + #define PWR_ON_S_DELAY_CNT GENMASK(7, 0) /* pwr_on_s assert delay during power-up */ + #define ISO_DELAY_CNT GENMASK(15, 8) /* ISO assert delay during power-down */ + #define ISOOFF_DELAY_CNT GENMASK(23, 16) /* ISO de-assert delay during power-up */ + #define RST_DELAY_CNT GENMASK(31, 24) /* pwr_rsb_b assert delay during power-down */ + +static int clk_mt8173_mfgtop_power_on(struct generic_pm_domain *domain) +{ + struct mt8173_mfgtop_data *data = container_of(domain, struct mt8173_mfgtop_data, genpd); + int ret; + + /* drives internal power management */ + ret = clk_prepare_enable(data->clk_26m); + if (ret) + return ret; + + /* Power on/off delays for various signals */ + regmap_write(data->regmap, MFG_ACTIVE_POWER_CON0, + FIELD_PREP(RST_B_DELAY_CNT, 77) | + FIELD_PREP(CLK_EN_DELAY_CNT, 61) | + FIELD_PREP(CLK_DIS_DELAY_CNT, 60) | + FIELD_PREP(ACTIVE_PWRCTL_EN, 0)); + regmap_write(data->regmap, MFG_ACTIVE_POWER_CON1, + FIELD_PREP(PWR_ON_S_DELAY_CNT, 11) | + FIELD_PREP(ISO_DELAY_CNT, 68) | + FIELD_PREP(ISOOFF_DELAY_CNT, 69) | + FIELD_PREP(RST_DELAY_CNT, 77)); + + /* Magic numbers related to core switch sequence and delays */ + regmap_write(data->regmap, 0xe0, 0x7a710184); + regmap_write(data->regmap, 0xe4, 0x835f6856); + regmap_write(data->regmap, 0xe8, 0x002b0234); + regmap_write(data->regmap, 0xec, 0x80000000); + regmap_write(data->regmap, 0xa0, 0x08000000); + + return 0; +} + +static int clk_mt8173_mfgtop_power_off(struct generic_pm_domain *domain) +{ + struct mt8173_mfgtop_data *data = container_of(domain, struct mt8173_mfgtop_data, genpd); + + /* Magic numbers related to core switch sequence and delays */ + regmap_write(data->regmap, 0xec, 0); + + /* drives internal power management */ + clk_disable_unprepare(data->clk_26m); + + return 0; +} + +static int clk_mt8173_mfgtop_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct mt8173_mfgtop_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + + data->clk_data = mtk_devm_alloc_clk_data(dev, ARRAY_SIZE(mfg_clks)); + if (!data->clk_data) + return -ENOMEM; + + /* MTK clock gates also uses regmap */ + data->regmap = device_node_to_regmap(node); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), "Failed to get regmap\n"); + + data->child_pd.np = node; + data->child_pd.args_count = 0; + ret = of_parse_phandle_with_args(node, "power-domains", "#power-domain-cells", 0, + &data->parent_pd); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse power domain\n"); + + devm_pm_runtime_enable(dev); + /* + * Do a pm_runtime_resume_and_get() to workaround a possible + * deadlock between clk_register() and the genpd framework. + */ + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err_probe(dev, ret, "Failed to runtime resume device\n"); + goto put_of_node; + } + + ret = mtk_clk_register_gates(dev, node, mfg_clks, ARRAY_SIZE(mfg_clks), + data->clk_data); + if (ret) { + dev_err_probe(dev, ret, "Failed to register clock gates\n"); + goto put_pm_runtime; + } + + data->clk_26m = clk_hw_get_clk(data->clk_data->hws[CLK_MFG_26M], "26m"); + if (IS_ERR(data->clk_26m)) { + ret = dev_err_probe(dev, PTR_ERR(data->clk_26m), "Failed to get 26 MHz clock\n"); + goto unregister_clks; + } + + ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, data->clk_data); + if (ret) { + dev_err_probe(dev, ret, "Failed to add clk OF provider\n"); + goto put_26m_clk; + } + + data->genpd.name = "mfg-top"; + data->genpd.power_on = clk_mt8173_mfgtop_power_on; + data->genpd.power_off = clk_mt8173_mfgtop_power_off; + ret = pm_genpd_init(&data->genpd, NULL, true); + if (ret) { + dev_err_probe(dev, ret, "Failed to add power domain\n"); + goto del_clk_provider; + } + + ret = of_genpd_add_provider_simple(node, &data->genpd); + if (ret) { + dev_err_probe(dev, ret, "Failed to add power domain OF provider\n"); + goto remove_pd; + } + + ret = of_genpd_add_subdomain(&data->parent_pd, &data->child_pd); + if (ret) { + dev_err_probe(dev, ret, "Failed to link PM domains\n"); + goto del_pd_provider; + } + + pm_runtime_put(dev); + return 0; + +del_pd_provider: + of_genpd_del_provider(node); +remove_pd: + pm_genpd_remove(&data->genpd); +del_clk_provider: + of_clk_del_provider(node); +put_26m_clk: + clk_put(data->clk_26m); +unregister_clks: + mtk_clk_unregister_gates(mfg_clks, ARRAY_SIZE(mfg_clks), data->clk_data); +put_pm_runtime: + pm_runtime_put_sync(dev); +put_of_node: + of_node_put(data->parent_pd.np); + return ret; +} + +static void clk_mt8173_mfgtop_remove(struct platform_device *pdev) +{ + struct mt8173_mfgtop_data *data = platform_get_drvdata(pdev); + struct device_node *node = pdev->dev.of_node; + + of_genpd_remove_subdomain(&data->parent_pd, &data->child_pd); + of_genpd_del_provider(node); + pm_genpd_remove(&data->genpd); + of_clk_del_provider(node); + clk_put(data->clk_26m); + mtk_clk_unregister_gates(mfg_clks, ARRAY_SIZE(mfg_clks), data->clk_data); + of_node_put(data->parent_pd.np); +} + +static const struct of_device_id of_match_clk_mt8173_mfgtop[] = { + { .compatible = "mediatek,mt8173-mfgtop" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_match_clk_mt8173_mfgtop); + +static struct platform_driver clk_mt8173_mfgtop_drv = { + .probe = clk_mt8173_mfgtop_probe, + .remove = clk_mt8173_mfgtop_remove, + .driver = { + .name = "clk-mt8173-mfgtop", + .of_match_table = of_match_clk_mt8173_mfgtop, + }, +}; +module_platform_driver(clk_mt8173_mfgtop_drv); + +MODULE_DESCRIPTION("MediaTek MT8173 mfgtop clock driver"); +MODULE_LICENSE("GPL"); From 51be18310cd82fc4f1692d9cb7c4f53d26cacd74 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 24 Jun 2026 13:44:59 +0800 Subject: [PATCH 37/45] dt-bindings: clock: ast2700: add PECI clock Add SCU1_CLK_PECI for the SoC1 PECI controller clock source, and SCU1_CLK_HPLL_DIV4 which serves as one of the PECI clock mux parents. Signed-off-by: Ryan Chen Acked-by: Conor Dooley Signed-off-by: Brian Masney --- include/dt-bindings/clock/aspeed,ast2700-scu.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/dt-bindings/clock/aspeed,ast2700-scu.h b/include/dt-bindings/clock/aspeed,ast2700-scu.h index bacf712e8e04..138f78ce5f07 100644 --- a/include/dt-bindings/clock/aspeed,ast2700-scu.h +++ b/include/dt-bindings/clock/aspeed,ast2700-scu.h @@ -163,5 +163,7 @@ #define SCU1_CLK_GATE_PORTDUSB2CLK 85 #define SCU1_CLK_GATE_LTPI1TXCLK 86 #define SCU1_CLK_I3C 87 +#define SCU1_CLK_HPLL_DIV4 88 +#define SCU1_CLK_PECI 89 #endif From 5ffa9d308c7a30192f1b05ee0bfd9e1976493a54 Mon Sep 17 00:00:00 2001 From: Ryan Chen Date: Wed, 24 Jun 2026 13:45:00 +0800 Subject: [PATCH 38/45] clk: aspeed: add AST2700 PECI clock Register the SoC1 PECI clock as a mux selected by SCU1_CLK_SEL2 bit 16, choosing between the 25MHz CLKIN and HPLL/4. Add the HPLL/4 fixed factor clock to serve as the second mux parent. Signed-off-by: Ryan Chen Signed-off-by: Brian Masney --- drivers/clk/aspeed/clk-ast2700.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/clk/aspeed/clk-ast2700.c b/drivers/clk/aspeed/clk-ast2700.c index 8b7b382f6f3e..5f499ec0e1d1 100644 --- a/drivers/clk/aspeed/clk-ast2700.c +++ b/drivers/clk/aspeed/clk-ast2700.c @@ -273,6 +273,13 @@ static const unsigned int sdclk_parent_ids[] = { static const struct clk_hw *sdclk_parent_hws[ARRAY_SIZE(sdclk_parent_ids)]; +static const unsigned int peciclk_parent_ids[] = { + SCU1_CLKIN, + SCU1_CLK_HPLL_DIV4 +}; + +static const struct clk_hw *peciclk_parent_hws[ARRAY_SIZE(peciclk_parent_ids)]; + #define FIXED_CLK(_id, _name, _rate) \ { \ .id = _id, \ @@ -458,6 +465,7 @@ static const struct ast2700_clk_info ast2700_scu1_clk_info[] __initconst = { PLL_CLK(SCU1_CLK_HPLL, CLK_PLL, "soc1-hpll", SCU1_CLKIN, SCU1_HPLL_PARAM), PLL_CLK(SCU1_CLK_APLL, CLK_PLL, "soc1-apll", SCU1_CLKIN, SCU1_APLL_PARAM), PLL_CLK(SCU1_CLK_DPLL, CLK_PLL, "soc1-dpll", SCU1_CLKIN, SCU1_DPLL_PARAM), + FIXED_FACTOR_CLK(SCU1_CLK_HPLL_DIV4, "soc1-hpll_div4", SCU1_CLK_HPLL, 1, 4), FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV2, "soc1-apll_div2", SCU1_CLK_APLL, 1, 2), FIXED_FACTOR_CLK(SCU1_CLK_APLL_DIV4, "soc1-apll_div4", SCU1_CLK_APLL, 1, 4), FIXED_FACTOR_CLK(SCU1_CLK_CAN, "canclk", SCU1_CLK_APLL, 1, 10), @@ -481,6 +489,8 @@ static const struct ast2700_clk_info ast2700_scu1_clk_info[] __initconst = { uxclk_parent_hws, SCU1_CLK_SEL2, 0, 2), MUX_CLK(SCU1_CLK_HUXCLK, "huxclk", uxclk_parent_ids, ARRAY_SIZE(uxclk_parent_ids), uxclk_parent_hws, SCU1_CLK_SEL2, 3, 2), + MUX_CLK(SCU1_CLK_PECI, "peciclk", peciclk_parent_ids, ARRAY_SIZE(peciclk_parent_ids), + peciclk_parent_hws, SCU1_CLK_SEL2, 16, 1), DIVIDER_CLK(SCU1_CLK_SDCLK, "sdclk", SCU1_CLK_SDMUX, SCU1_CLK_SEL1, 14, 3, ast2700_clk_div_table), PLL_CLK(SCU1_CLK_UARTX, CLK_UART_PLL, "uartxclk", SCU1_CLK_UXCLK, SCU1_UXCLK_CTRL), From 46e31d69a547530fbef499bde888b6bb45a1deac Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 25 Jun 2026 11:30:52 +0300 Subject: [PATCH 39/45] clk: tegra: support 48MHz clock for pll_p_out1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UEFI on Surface2 sets pll_p_out1 to 48MHz which is not supported by kernel and causes BUG() early on. Add 48MHz clock support for pll_p_out1. Acked-by: Thierry Reding Signed-off-by: Dmitry Osipenko Signed-off-by: Jonas Schwöbel Signed-off-by: Svyatoslav Ryhel Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-pll.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clk/tegra/clk-pll.c b/drivers/clk/tegra/clk-pll.c index d86003b6d94f..adfb74f111ef 100644 --- a/drivers/clk/tegra/clk-pll.c +++ b/drivers/clk/tegra/clk-pll.c @@ -575,6 +575,7 @@ static int _calc_rate(struct clk_hw *hw, struct tegra_clk_pll_freq_table *cfg, break; case 9600000: case 28800000: + case 48000000: /* * PLL_P_OUT1 rate is not listed in PLLA table */ From d85028afefad94b4b2f9f7f36997f18d7e8984d2 Mon Sep 17 00:00:00 2001 From: Shubhrajyoti Datta Date: Mon, 29 Jun 2026 11:21:46 +0530 Subject: [PATCH 40/45] clk: clocking-wizard: Program PLL CP/RES and lock parameters on reconfig When dynamically reconfiguring the PLL multiplier, the charge pump (CP), loop filter resistance (RES), and lock timing parameters must be updated to match the new multiplier value. Without this, the PLL may fail to lock or exhibit jitter at certain multiply factors. Add lookup tables for CP/RES and lock timing values indexed by multiplier range, and program registers for chargepump and lock delay during dynamic reconfiguration. Signed-off-by: Shubhrajyoti Datta Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 134 +++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index 4a0136349f71..f5d14543167a 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -35,6 +35,10 @@ #define WZRD_DIVCLK 21 #define WZRD_CLKFBOUT_4 51 #define WZRD_CLKFBOUT_3 48 +#define WZRD_CP 18 +#define WZRD_LOCK 27 +#define WZRD_LOCK_REF_DLY 28 +#define WZRD_RES 30 #define WZRD_DUTY_CYCLE 2 #define WZRD_O_DIV 4 @@ -49,6 +53,10 @@ #define WZRD_P5FEDGE_SHIFT 15 #define WZRD_CLKOUT0_PREDIV2 BIT(11) #define WZRD_EDGE_SHIFT 8 +#define WZRD_CP_MASK GENMASK(3, 0) +#define WZRD_RES_MASK GENMASK(4, 1) +#define WZRD_LOCK_FB_DLY_MASK GENMASK(14, 10) +#define WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK GENMASK(14, 10) #define WZRD_CLKFBOUT_MULT_SHIFT 8 #define WZRD_CLKFBOUT_MULT_MASK (0xff << WZRD_CLKFBOUT_MULT_SHIFT) @@ -440,6 +448,130 @@ static int clk_wzrd_reconfig(struct clk_wzrd_divider *divider, void __iomem *div WZRD_USEC_POLL, WZRD_TIMEOUT_POLL); } +struct wzrd_pll_filter { + u32 m_min; + u32 m_max; + u32 cp; + u32 res; +}; + +static const struct wzrd_pll_filter wzrd_cp_res_table[] = { + { 4, 4, 5, 15 }, + { 5, 5, 6, 15 }, + { 6, 6, 7, 15 }, + { 7, 7, 13, 15 }, + { 8, 8, 14, 15 }, + { 9, 9, 15, 15 }, + { 10, 10, 14, 7 }, + { 11, 11, 15, 7 }, + { 12, 13, 15, 11 }, + { 14, 14, 15, 13 }, + { 15, 15, 15, 3 }, + { 16, 17, 14, 5 }, + { 18, 19, 15, 5 }, + { 20, 21, 15, 9 }, + { 22, 23, 14, 14 }, + { 24, 26, 15, 14 }, + { 27, 28, 14, 1 }, + { 29, 33, 15, 1 }, + { 34, 37, 14, 6 }, + { 38, 44, 15, 6 }, + { 45, 57, 15, 10 }, + { 58, 63, 13, 12 }, + { 64, 70, 14, 12 }, + { 71, 86, 15, 12 }, + { 87, 94, 14, 2 }, + { 95, 145, 15, 2 }, + { 146, 163, 12, 4 }, + { 164, 181, 13, 4 }, + { 182, 200, 14, 4 }, + { 201, 273, 15, 4 }, + { 274, 300, 13, 8 }, + { 301, 325, 14, 8 }, + { 326, 432, 15, 8 }, +}; + +struct wzrd_lock_timing { + u32 m_min; + u32 m_max; + u32 ref_dly; + u32 fb_dly; + u32 lock_cnt; +}; + +static const struct wzrd_lock_timing wzrd_lock_table[] = { + { 4, 4, 4, 4, 1000 }, + { 5, 5, 6, 6, 1000 }, + { 6, 8, 7, 7, 1000 }, + { 9, 12, 8, 8, 1000 }, + { 13, 13, 10, 10, 1000 }, + { 14, 16, 13, 13, 1000 }, + { 17, 17, 16, 16, 825 }, + { 18, 18, 16, 16, 750 }, + { 19, 20, 16, 16, 700 }, + { 21, 21, 16, 16, 650 }, + { 22, 23, 16, 16, 625 }, + { 24, 24, 16, 16, 575 }, + { 25, 25, 16, 16, 550 }, + { 26, 28, 16, 16, 525 }, + { 29, 30, 16, 16, 475 }, + { 31, 31, 16, 16, 450 }, + { 32, 33, 16, 16, 425 }, + { 34, 36, 16, 16, 400 }, + { 37, 37, 16, 16, 375 }, + { 38, 40, 16, 16, 350 }, + { 41, 43, 16, 16, 325 }, + { 44, 47, 16, 16, 300 }, + { 48, 51, 16, 16, 275 }, + { 52, 205, 16, 16, 250 }, + { 206, 432, 16, 16, 225 }, +}; + +static void clk_wzrd_update_cp_res_lock(struct clk_wzrd_divider *divider, u32 m) +{ + u32 lock_ref_dly = 16, lock_fb_dly = 16, lock_cnt = 250, cp = 15, res = 15; + void __iomem *base = divider->base; + u32 reg; + int i; + + for (i = 0; i < ARRAY_SIZE(wzrd_cp_res_table); i++) { + if (m >= wzrd_cp_res_table[i].m_min && + m <= wzrd_cp_res_table[i].m_max) { + cp = wzrd_cp_res_table[i].cp; + res = wzrd_cp_res_table[i].res; + break; + } + } + + for (i = 0; i < ARRAY_SIZE(wzrd_lock_table); i++) { + if (m >= wzrd_lock_table[i].m_min && + m <= wzrd_lock_table[i].m_max) { + lock_ref_dly = wzrd_lock_table[i].ref_dly; + lock_fb_dly = wzrd_lock_table[i].fb_dly; + lock_cnt = wzrd_lock_table[i].lock_cnt; + break; + } + } + + reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_CP)); + reg &= ~WZRD_CP_MASK; + reg |= FIELD_PREP(WZRD_CP_MASK, cp); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_CP)); + + reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_RES)); + reg &= ~WZRD_RES_MASK; + reg |= FIELD_PREP(WZRD_RES_MASK, res); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_RES)); + + reg = lock_cnt | FIELD_PREP(WZRD_LOCK_FB_DLY_MASK, lock_fb_dly); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_LOCK)); + + reg = readl(base + WZRD_CLK_CFG_REG(1, WZRD_LOCK_REF_DLY)); + reg &= ~WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK; + reg |= FIELD_PREP(WZRD_LOCK_REF_DLY_LOCK_REF_DLY_MASK, lock_ref_dly); + writel(reg, base + WZRD_CLK_CFG_REG(1, WZRD_LOCK_REF_DLY)); +} + static int clk_wzrd_dynamic_ver_all_nolock(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -471,6 +603,8 @@ static int clk_wzrd_dynamic_ver_all_nolock(struct clk_hw *hw, unsigned long rate writel(regval1, divider->base + WZRD_CLK_CFG_REG(1, WZRD_CLKFBOUT_2)); + clk_wzrd_update_cp_res_lock(divider, m); + value2 = divider->d; edged = value2 % WZRD_DUTY_CYCLE; regh = (value2 / WZRD_DUTY_CYCLE); From fd0e3e4edea6a3e4da91be608ca2fb9b348f9e32 Mon Sep 17 00:00:00 2001 From: Akari Tsuyukusa Date: Mon, 29 Jun 2026 22:20:43 +0900 Subject: [PATCH 41/45] clk: mediatek: mt8135: Fix inverted gate control for devapc_ck The devapc_ck (CLK_INFRA_DEVAPC) on MT8135 is currently using "mtk_clk_gate_ops_setclr". However, checking the downstream kernel reveals that this clock is configured with set:enable and clr:disable making "mtk_clk_gate_ops_setclr_inv" the appropriate choice. But, it is strange that some downstream kernels are not like that. Amazon: INV ChromiumOS (early): not INV ChromiumOS 3.16 to 3.18-revew-v2: INV ChromiumOS 3.18-review-v3 and later (sent to kernel.org): not INV Link: https://github.com/amazon-oss/android_kernel_amazon_mt8135/blob/e2b2163a8ec4a7c8d961c89003a15b4ba0f0e371/arch/arm/mach-mt8135/mt_clkmgr.c#L1022-L1028 Link: https://github.com/mtk09422/chromiumos-third_party-kernel-mediatek/blob/4b624ee66e65d5dcd43fca36b313086efae8922a/arch/arm/boot/dts/mt8135-clocks.dtsi#L944-L948 Link: https://github.com/mtk09422/chromiumos-third_party-kernel-mediatek/blob/decd80c01d0dbe9f3afa8ff72273b5618b418180/drivers/clk/mediatek/clk-mt8135.c#L881-L882 Link: https://github.com/mtk09422/chromiumos-third_party-kernel-mediatek/blob/9b6f06cb7637100aa1a42e1fc351b36b384a1c54/drivers/clk/mediatek/clk-mt8135.c#L450 Fixes: a8aede794843 ("clk: mediatek: Add basic clocks for Mediatek MT8135.") Signed-off-by: Akari Tsuyukusa Signed-off-by: Brian Masney --- drivers/clk/mediatek/clk-mt8135.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/clk/mediatek/clk-mt8135.c b/drivers/clk/mediatek/clk-mt8135.c index 084e48a554c2..1d20e15608f7 100644 --- a/drivers/clk/mediatek/clk-mt8135.c +++ b/drivers/clk/mediatek/clk-mt8135.c @@ -409,6 +409,9 @@ static const struct mtk_gate_regs infra_cg_regs = { GATE_MTK_FLAGS(_id, _name, _parent, &infra_cg_regs, _shift, \ &mtk_clk_gate_ops_setclr, CLK_IS_CRITICAL) +#define GATE_ICG_INV(_id, _name, _parent, _shift) \ + GATE_MTK(_id, _name, _parent, &infra_cg_regs, _shift, &mtk_clk_gate_ops_setclr_inv) + static const struct mtk_gate infra_clks[] = { GATE_DUMMY(CLK_DUMMY, "infra_dummy"), GATE_ICG(CLK_INFRA_PMIC_WRAP, "pmic_wrap_ck", "axi_sel", 23), @@ -419,7 +422,7 @@ static const struct mtk_gate infra_clks[] = { GATE_ICG(CLK_INFRA_CPUM, "cpum_ck", "cpum_tck_in", 15), GATE_ICG_AO(CLK_INFRA_M4U, "m4u_ck", "mem_sel", 8), GATE_ICG(CLK_INFRA_MFGAXI, "mfgaxi_ck", "axi_sel", 7), - GATE_ICG(CLK_INFRA_DEVAPC, "devapc_ck", "axi_sel", 6), + GATE_ICG_INV(CLK_INFRA_DEVAPC, "devapc_ck", "axi_sel", 6), GATE_ICG(CLK_INFRA_AUDIO, "audio_ck", "aud_intbus_sel", 5), GATE_ICG(CLK_INFRA_MFG_BUS, "mfg_bus_ck", "axi_sel", 2), GATE_ICG(CLK_INFRA_SMI, "smi_ck", "smi_sel", 1), From 006ba909c240b832c876b7aed29c6beec9d1f5e5 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Mon, 29 Jun 2026 15:53:44 -0500 Subject: [PATCH 42/45] clk: clocking-wizard: fix clock difference detection Reviewed-by: Shubhrajyoti Datta Reviewed-by: Brian Masney Signed-off-by: Colin Foster Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index f5d14543167a..05168de0c291 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -414,7 +414,7 @@ static int clk_wzrd_get_divisors(struct clk_hw *hw, unsigned long rate, if (o < omin || o > omax) continue; freq = DIV_ROUND_CLOSEST_ULL(vco_freq, o); - diff = freq - rate; + diff = abs(freq - rate); if (diff < best_diff) { best_diff = diff; divider->m = m >> 3; From 9329714414d1f3902fae7100adb358ec946484b5 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Mon, 29 Jun 2026 15:53:45 -0500 Subject: [PATCH 43/45] clk: clocking-wizard: optimize clock search When an exact clock match is found, there is no need to continue searching. This process was optimized for versal as part of 'commit e0a94c6bb5b4 ("clk: xilinx: Optimize divisor search in clk_wzrd_get_divisors_ver()")' but that logic wasn't applied to the non-versal driver. Apply this fast-exit logic to the non-versal driver. Reviewed-by: Shubhrajyoti Datta Reviewed-by: Brian Masney Signed-off-by: Colin Foster Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index 05168de0c291..11010257ed2e 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -422,6 +422,9 @@ static int clk_wzrd_get_divisors(struct clk_hw *hw, unsigned long rate, divider->d = d; divider->o = o >> 3; divider->o_frac = (o - (divider->o << 3)) * 125; + + if (!diff) + return 0; } } } From 54ed94b709e03fb065d426f378e9d415fa7ee939 Mon Sep 17 00:00:00 2001 From: Colin Foster Date: Mon, 29 Jun 2026 15:53:46 -0500 Subject: [PATCH 44/45] clk: clocking-wizard: remove 20kHz restriction Reviewed-by: Shubhrajyoti Datta Reviewed-by: Brian Masney Signed-off-by: Colin Foster Signed-off-by: Brian Masney --- drivers/clk/xilinx/clk-xlnx-clock-wizard.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c index 11010257ed2e..1ee30a0c65b1 100644 --- a/drivers/clk/xilinx/clk-xlnx-clock-wizard.c +++ b/drivers/clk/xilinx/clk-xlnx-clock-wizard.c @@ -113,7 +113,6 @@ #define VER_WZRD_VCO_MAX 4320000000ULL #define VER_WZRD_O_MIN 2 #define VER_WZRD_O_MAX 511 -#define WZRD_MIN_ERR 20000 #define WZRD_FRAC_POINTS 1000 /* Get the mask from width */ @@ -428,7 +427,7 @@ static int clk_wzrd_get_divisors(struct clk_hw *hw, unsigned long rate, } } } - return best_diff < WZRD_MIN_ERR ? 0 : -EBUSY; + return best_diff != -1ULL ? 0 : -EBUSY; } static int clk_wzrd_reconfig(struct clk_wzrd_divider *divider, void __iomem *div_addr) From cf561283254dd9f55805800237e6f82f08d91497 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Thu, 25 Jun 2026 11:19:06 +0300 Subject: [PATCH 45/45] clk: tegra: set up proper EMC clock implementation for Tegra114 Remove current emc and emc_mux clocks and replace them with the proper EMC clock implementation for correct EMC driver support. Acked-by: Thierry Reding Signed-off-by: Svyatoslav Ryhel Reviewed-by: Mikko Perttunen Signed-off-by: Brian Masney --- drivers/clk/tegra/clk-tegra114.c | 39 ++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/drivers/clk/tegra/clk-tegra114.c b/drivers/clk/tegra/clk-tegra114.c index a4f40533cc43..f1bca81b7776 100644 --- a/drivers/clk/tegra/clk-tegra114.c +++ b/drivers/clk/tegra/clk-tegra114.c @@ -620,10 +620,6 @@ static const char *mux_plld_out0_plld2_out0[] = { }; #define mux_plld_out0_plld2_out0_idx NULL -static const char *mux_pllmcp_clkm[] = { - "pll_m_out0", "pll_c_out0", "pll_p_out0", "clk_m", "pll_m_ud", -}; - static const struct clk_div_table pll_re_div_table[] = { { .val = 0, .div = 1 }, { .val = 1, .div = 2 }, @@ -670,7 +666,6 @@ static struct tegra_clk tegra114_clks[tegra_clk_max] __initdata = { [tegra_clk_csi] = { .dt_id = TEGRA114_CLK_CSI, .present = true }, [tegra_clk_i2c2] = { .dt_id = TEGRA114_CLK_I2C2, .present = true }, [tegra_clk_uartc] = { .dt_id = TEGRA114_CLK_UARTC, .present = true }, - [tegra_clk_emc] = { .dt_id = TEGRA114_CLK_EMC, .present = true }, [tegra_clk_usb2] = { .dt_id = TEGRA114_CLK_USB2, .present = true }, [tegra_clk_usb3] = { .dt_id = TEGRA114_CLK_USB3, .present = true }, [tegra_clk_vde_8] = { .dt_id = TEGRA114_CLK_VDE, .present = true }, @@ -1051,14 +1046,7 @@ static __init void tegra114_periph_clk_init(void __iomem *clk_base, periph_clk_enb_refcnt); clks[TEGRA114_CLK_CSUS] = clk; - /* emc mux */ - clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm, - ARRAY_SIZE(mux_pllmcp_clkm), - CLK_SET_RATE_NO_REPARENT, - clk_base + CLK_SOURCE_EMC, - 29, 3, 0, &emc_lock); - - clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC, + clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC, &emc_lock); clks[TEGRA114_CLK_MC] = clk; @@ -1324,6 +1312,26 @@ static int tegra114_reset_deassert(unsigned long id) return 0; } +static struct clk *tegra114_clk_src_onecell_get(struct of_phandle_args *clkspec, + void *data) +{ + struct clk_hw *hw; + struct clk *clk; + + clk = of_clk_src_onecell_get(clkspec, data); + if (IS_ERR(clk)) + return clk; + + hw = __clk_get_hw(clk); + + if (clkspec->args[0] == TEGRA114_CLK_EMC) { + if (!tegra124_clk_emc_driver_available(hw)) + return ERR_PTR(-EPROBE_DEFER); + } + + return clk; +} + static void __init tegra114_clock_init(struct device_node *np) { struct device_node *node; @@ -1371,7 +1379,10 @@ static void __init tegra114_clock_init(struct device_node *np) tegra_init_special_resets(1, tegra114_reset_assert, tegra114_reset_deassert); - tegra_add_of_provider(np, of_clk_src_onecell_get); + tegra_add_of_provider(np, tegra114_clk_src_onecell_get); + clks[TEGRA114_CLK_EMC] = tegra124_clk_register_emc(clk_base, np, + &emc_lock); + tegra_register_devclks(devclks, ARRAY_SIZE(devclks)); tegra_clk_apply_init_table = tegra114_clock_apply_init_table;