1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2015 Altera Corporation. All rights reserved |
4 | */ |
5 | #include <linux/slab.h> |
6 | #include <linux/clk-provider.h> |
7 | #include <linux/io.h> |
8 | #include <linux/mfd/syscon.h> |
9 | #include <linux/of.h> |
10 | #include <linux/regmap.h> |
11 | |
12 | #include "clk.h" |
13 | |
14 | #define streq(a, b) (strcmp((a), (b)) == 0) |
15 | |
16 | #define to_socfpga_gate_clk(p) container_of(p, struct socfpga_gate_clk, hw.hw) |
17 | |
18 | /* SDMMC Group for System Manager defines */ |
19 | #define SYSMGR_SDMMCGRP_CTRL_OFFSET 0x28 |
20 | |
21 | static unsigned long socfpga_gate_clk_recalc_rate(struct clk_hw *hwclk, |
22 | unsigned long parent_rate) |
23 | { |
24 | struct socfpga_gate_clk *socfpgaclk = to_socfpga_gate_clk(hwclk); |
25 | u32 div = 1, val; |
26 | |
27 | if (socfpgaclk->fixed_div) |
28 | div = socfpgaclk->fixed_div; |
29 | else if (socfpgaclk->div_reg) { |
30 | val = readl(addr: socfpgaclk->div_reg) >> socfpgaclk->shift; |
31 | val &= GENMASK(socfpgaclk->width - 1, 0); |
32 | div = (1 << val); |
33 | } |
34 | |
35 | return parent_rate / div; |
36 | } |
37 | |
38 | static struct clk_ops gateclk_ops = { |
39 | .recalc_rate = socfpga_gate_clk_recalc_rate, |
40 | }; |
41 | |
42 | static void __init __socfpga_gate_init(struct device_node *node, |
43 | const struct clk_ops *ops) |
44 | { |
45 | u32 clk_gate[2]; |
46 | u32 div_reg[3]; |
47 | u32 fixed_div; |
48 | struct clk_hw *hw_clk; |
49 | struct socfpga_gate_clk *socfpga_clk; |
50 | const char *clk_name = node->name; |
51 | const char *parent_name[SOCFPGA_MAX_PARENTS]; |
52 | struct clk_init_data init; |
53 | int rc; |
54 | |
55 | socfpga_clk = kzalloc(size: sizeof(*socfpga_clk), GFP_KERNEL); |
56 | if (WARN_ON(!socfpga_clk)) |
57 | return; |
58 | |
59 | rc = of_property_read_u32_array(np: node, propname: "clk-gate" , out_values: clk_gate, sz: 2); |
60 | if (rc) |
61 | clk_gate[0] = 0; |
62 | |
63 | if (clk_gate[0]) { |
64 | socfpga_clk->hw.reg = clk_mgr_a10_base_addr + clk_gate[0]; |
65 | socfpga_clk->hw.bit_idx = clk_gate[1]; |
66 | |
67 | gateclk_ops.enable = clk_gate_ops.enable; |
68 | gateclk_ops.disable = clk_gate_ops.disable; |
69 | } |
70 | |
71 | rc = of_property_read_u32(np: node, propname: "fixed-divider" , out_value: &fixed_div); |
72 | if (rc) |
73 | socfpga_clk->fixed_div = 0; |
74 | else |
75 | socfpga_clk->fixed_div = fixed_div; |
76 | |
77 | rc = of_property_read_u32_array(np: node, propname: "div-reg" , out_values: div_reg, sz: 3); |
78 | if (!rc) { |
79 | socfpga_clk->div_reg = clk_mgr_a10_base_addr + div_reg[0]; |
80 | socfpga_clk->shift = div_reg[1]; |
81 | socfpga_clk->width = div_reg[2]; |
82 | } else { |
83 | socfpga_clk->div_reg = NULL; |
84 | } |
85 | |
86 | of_property_read_string(np: node, propname: "clock-output-names" , out_string: &clk_name); |
87 | |
88 | init.name = clk_name; |
89 | init.ops = ops; |
90 | init.flags = 0; |
91 | |
92 | init.num_parents = of_clk_parent_fill(np: node, parents: parent_name, SOCFPGA_MAX_PARENTS); |
93 | init.parent_names = parent_name; |
94 | socfpga_clk->hw.hw.init = &init; |
95 | hw_clk = &socfpga_clk->hw.hw; |
96 | |
97 | rc = clk_hw_register(NULL, hw: hw_clk); |
98 | if (rc) { |
99 | pr_err("Could not register clock:%s\n" , clk_name); |
100 | goto err_clk_hw_register; |
101 | } |
102 | |
103 | rc = of_clk_add_hw_provider(np: node, get: of_clk_hw_simple_get, data: hw_clk); |
104 | if (rc) { |
105 | pr_err("Could not register clock provider for node:%s\n" , |
106 | clk_name); |
107 | goto err_of_clk_add_hw_provider; |
108 | } |
109 | |
110 | return; |
111 | |
112 | err_of_clk_add_hw_provider: |
113 | clk_hw_unregister(hw: hw_clk); |
114 | err_clk_hw_register: |
115 | kfree(objp: socfpga_clk); |
116 | } |
117 | |
118 | void __init socfpga_a10_gate_init(struct device_node *node) |
119 | { |
120 | __socfpga_gate_init(node, ops: &gateclk_ops); |
121 | } |
122 | |