Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clk: rockchip: implement linked gate clock support

Recent Rockchip SoCs have a new hardware block called Native Interface
Unit (NIU), which gates clocks to devices behind them. These clock
gates will only have a running output clock when all of the following
conditions are met:

1. the parent clock is enabled
2. the enable bit is set correctly
3. the linked clock is enabled

To handle them this code registers them as a normal gate type clock,
which takes care of condition 1 + 2. The linked clock is handled by
using runtime PM clocks. Handling it via runtime PM requires setting
up a struct device for each of these clocks with a driver attached
to use the correct runtime PM operations. Thus the complete handling
of these clocks has been moved into its own driver.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Link: https://lore.kernel.org/r/20241211165957.94922-5-sebastian.reichel@collabora.com
Signed-off-by: Heiko Stuebner <heiko@sntech.de>

authored by

Sebastian Reichel and committed by
Heiko Stuebner
c62fa612 fe0fb667

+165 -21
+1
drivers/clk/rockchip/Makefile
··· 13 13 clk-rockchip-y += clk-mmc-phase.o 14 14 clk-rockchip-y += clk-muxgrf.o 15 15 clk-rockchip-y += clk-ddr.o 16 + clk-rockchip-y += gate-link.o 16 17 clk-rockchip-$(CONFIG_RESET_CONTROLLER) += softrst.o 17 18 18 19 obj-$(CONFIG_CLK_PX30) += clk-px30.o
+2 -21
drivers/clk/rockchip/clk-rk3588.c
··· 12 12 #include <dt-bindings/clock/rockchip,rk3588-cru.h> 13 13 #include "clk.h" 14 14 15 - /* 16 - * Recent Rockchip SoCs have a new hardware block called Native Interface 17 - * Unit (NIU), which gates clocks to devices behind them. These effectively 18 - * need two parent clocks. 19 - * 20 - * Downstream enables the linked clock via runtime PM whenever the gate is 21 - * enabled. This implementation uses separate clock nodes for each of the 22 - * linked gate clocks, which leaks parts of the clock tree into DT. 23 - * 24 - * The GATE_LINK macro instead takes the second parent via 'linkname', but 25 - * ignores the information. Once the clock framework is ready to handle it, the 26 - * information should be passed on here. But since these clocks are required to 27 - * access multiple relevant IP blocks, such as PCIe or USB, we mark all linked 28 - * clocks critical until a better solution is available. This will waste some 29 - * power, but avoids leaking implementation details into DT or hanging the 30 - * system. 31 - */ 32 - #define GATE_LINK(_id, cname, pname, linkedclk, f, o, b, gf) \ 33 - GATE(_id, cname, pname, f, o, b, gf) 34 15 #define RK3588_LINKED_CLK CLK_IS_CRITICAL 35 16 36 17 ··· 2494 2513 struct device *dev = &pdev->dev; 2495 2514 struct device_node *np = dev->of_node; 2496 2515 2497 - rockchip_clk_register_branches(ctx, rk3588_clk_branches, 2498 - ARRAY_SIZE(rk3588_clk_branches)); 2516 + rockchip_clk_register_late_branches(dev, ctx, rk3588_clk_branches, 2517 + ARRAY_SIZE(rk3588_clk_branches)); 2499 2518 2500 2519 rockchip_clk_finalize(ctx); 2501 2520
+52
drivers/clk/rockchip/clk.c
··· 19 19 #include <linux/clk-provider.h> 20 20 #include <linux/io.h> 21 21 #include <linux/mfd/syscon.h> 22 + #include <linux/platform_device.h> 22 23 #include <linux/regmap.h> 23 24 #include <linux/reboot.h> 24 25 ··· 469 468 } 470 469 EXPORT_SYMBOL_GPL(rockchip_clk_find_max_clk_id); 471 470 471 + static struct platform_device *rockchip_clk_register_gate_link( 472 + struct device *parent_dev, 473 + struct rockchip_clk_provider *ctx, 474 + struct rockchip_clk_branch *clkbr) 475 + { 476 + struct rockchip_gate_link_platdata gate_link_pdata = { 477 + .ctx = ctx, 478 + .clkbr = clkbr, 479 + }; 480 + 481 + struct platform_device_info pdevinfo = { 482 + .parent = parent_dev, 483 + .name = "rockchip-gate-link-clk", 484 + .id = clkbr->id, 485 + .fwnode = dev_fwnode(parent_dev), 486 + .of_node_reused = true, 487 + .data = &gate_link_pdata, 488 + .size_data = sizeof(gate_link_pdata), 489 + }; 490 + 491 + return platform_device_register_full(&pdevinfo); 492 + } 493 + 472 494 void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx, 473 495 struct rockchip_clk_branch *list, 474 496 unsigned int nr_clk) ··· 617 593 list->div_width, list->div_flags, 618 594 ctx->reg_base, &ctx->lock); 619 595 break; 596 + case branch_linked_gate: 597 + /* must be registered late, fall-through for error message */ 598 + break; 620 599 } 621 600 622 601 /* none of the cases above matched */ ··· 639 612 } 640 613 } 641 614 EXPORT_SYMBOL_GPL(rockchip_clk_register_branches); 615 + 616 + void rockchip_clk_register_late_branches(struct device *dev, 617 + struct rockchip_clk_provider *ctx, 618 + struct rockchip_clk_branch *list, 619 + unsigned int nr_clk) 620 + { 621 + unsigned int idx; 622 + 623 + for (idx = 0; idx < nr_clk; idx++, list++) { 624 + struct platform_device *pdev = NULL; 625 + 626 + switch (list->branch_type) { 627 + case branch_linked_gate: 628 + pdev = rockchip_clk_register_gate_link(dev, ctx, list); 629 + break; 630 + default: 631 + dev_err(dev, "unknown clock type %d\n", list->branch_type); 632 + break; 633 + } 634 + 635 + if (!pdev) 636 + dev_err(dev, "failed to register device for clock %s\n", list->name); 637 + } 638 + } 639 + EXPORT_SYMBOL_GPL(rockchip_clk_register_late_branches); 642 640 643 641 void rockchip_clk_register_armclk(struct rockchip_clk_provider *ctx, 644 642 unsigned int lookup_id,
+25
drivers/clk/rockchip/clk.h
··· 570 570 branch_divider, 571 571 branch_fraction_divider, 572 572 branch_gate, 573 + branch_linked_gate, 573 574 branch_mmc, 574 575 branch_inverter, 575 576 branch_factor, ··· 598 597 int gate_offset; 599 598 u8 gate_shift; 600 599 u8 gate_flags; 600 + unsigned int linked_clk_id; 601 601 struct rockchip_clk_branch *child; 602 602 }; 603 603 ··· 897 895 .gate_flags = gf, \ 898 896 } 899 897 898 + #define GATE_LINK(_id, cname, pname, linkedclk, f, o, b, gf) \ 899 + { \ 900 + .id = _id, \ 901 + .branch_type = branch_linked_gate, \ 902 + .name = cname, \ 903 + .parent_names = (const char *[]){ pname }, \ 904 + .linked_clk_id = linkedclk, \ 905 + .num_parents = 1, \ 906 + .flags = f, \ 907 + .gate_offset = o, \ 908 + .gate_shift = b, \ 909 + .gate_flags = gf, \ 910 + } 911 + 900 912 #define MMC(_id, cname, pname, offset, shift) \ 901 913 { \ 902 914 .id = _id, \ ··· 1050 1034 ctx->clk_data.clks[id] = clk; 1051 1035 } 1052 1036 1037 + struct rockchip_gate_link_platdata { 1038 + struct rockchip_clk_provider *ctx; 1039 + struct rockchip_clk_branch *clkbr; 1040 + }; 1041 + 1053 1042 struct rockchip_clk_provider *rockchip_clk_init(struct device_node *np, 1054 1043 void __iomem *base, unsigned long nr_clks); 1055 1044 struct rockchip_clk_provider *rockchip_clk_init_early(struct device_node *np, ··· 1067 1046 void rockchip_clk_register_branches(struct rockchip_clk_provider *ctx, 1068 1047 struct rockchip_clk_branch *list, 1069 1048 unsigned int nr_clk); 1049 + void rockchip_clk_register_late_branches(struct device *dev, 1050 + struct rockchip_clk_provider *ctx, 1051 + struct rockchip_clk_branch *list, 1052 + unsigned int nr_clk); 1070 1053 void rockchip_clk_register_plls(struct rockchip_clk_provider *ctx, 1071 1054 struct rockchip_pll_clock *pll_list, 1072 1055 unsigned int nr_pll, int grf_lock_offset);
+85
drivers/clk/rockchip/gate-link.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Copyright (c) 2024 Collabora Ltd. 4 + * Author: Sebastian Reichel <sebastian.reichel@collabora.com> 5 + */ 6 + 7 + #include <linux/clk.h> 8 + #include <linux/platform_device.h> 9 + #include <linux/pm_clock.h> 10 + #include <linux/pm_runtime.h> 11 + #include <linux/property.h> 12 + #include "clk.h" 13 + 14 + static int rk_clk_gate_link_register(struct device *dev, 15 + struct rockchip_clk_provider *ctx, 16 + struct rockchip_clk_branch *clkbr) 17 + { 18 + unsigned long flags = clkbr->flags | CLK_SET_RATE_PARENT; 19 + struct clk *clk; 20 + 21 + clk = clk_register_gate(dev, clkbr->name, clkbr->parent_names[0], 22 + flags, ctx->reg_base + clkbr->gate_offset, 23 + clkbr->gate_shift, clkbr->gate_flags, 24 + &ctx->lock); 25 + 26 + if (IS_ERR(clk)) 27 + return PTR_ERR(clk); 28 + 29 + rockchip_clk_set_lookup(ctx, clk, clkbr->id); 30 + return 0; 31 + } 32 + 33 + static int rk_clk_gate_link_probe(struct platform_device *pdev) 34 + { 35 + struct rockchip_gate_link_platdata *pdata; 36 + struct device *dev = &pdev->dev; 37 + struct clk *linked_clk; 38 + int ret; 39 + 40 + pdata = dev_get_platdata(dev); 41 + if (!pdata) 42 + return dev_err_probe(dev, -ENODEV, "missing platform data"); 43 + 44 + ret = devm_pm_runtime_enable(dev); 45 + if (ret) 46 + return ret; 47 + 48 + ret = devm_pm_clk_create(dev); 49 + if (ret) 50 + return ret; 51 + 52 + linked_clk = rockchip_clk_get_lookup(pdata->ctx, pdata->clkbr->linked_clk_id); 53 + ret = pm_clk_add_clk(dev, linked_clk); 54 + if (ret) 55 + return ret; 56 + 57 + ret = rk_clk_gate_link_register(dev, pdata->ctx, pdata->clkbr); 58 + if (ret) 59 + goto err; 60 + 61 + return 0; 62 + 63 + err: 64 + pm_clk_remove_clk(dev, linked_clk); 65 + return ret; 66 + } 67 + 68 + static const struct dev_pm_ops rk_clk_gate_link_pm_ops = { 69 + SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL) 70 + }; 71 + 72 + static struct platform_driver rk_clk_gate_link_driver = { 73 + .probe = rk_clk_gate_link_probe, 74 + .driver = { 75 + .name = "rockchip-gate-link-clk", 76 + .pm = &rk_clk_gate_link_pm_ops, 77 + .suppress_bind_attrs = true, 78 + }, 79 + }; 80 + 81 + static int __init rk_clk_gate_link_drv_register(void) 82 + { 83 + return platform_driver_register(&rk_clk_gate_link_driver); 84 + } 85 + core_initcall(rk_clk_gate_link_drv_register);