1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2014 Chen-Yu Tsai |
4 | * |
5 | * Chen-Yu Tsai <wens@csie.org> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/io.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/spinlock.h> |
13 | #include <linux/of_address.h> |
14 | |
15 | #define SUN8I_MBUS_ENABLE 31 |
16 | #define SUN8I_MBUS_MUX_SHIFT 24 |
17 | #define SUN8I_MBUS_MUX_MASK 0x3 |
18 | #define SUN8I_MBUS_DIV_SHIFT 0 |
19 | #define SUN8I_MBUS_DIV_WIDTH 3 |
20 | #define SUN8I_MBUS_MAX_PARENTS 4 |
21 | |
22 | static DEFINE_SPINLOCK(sun8i_a23_mbus_lock); |
23 | |
24 | static void __init sun8i_a23_mbus_setup(struct device_node *node) |
25 | { |
26 | int num_parents = of_clk_get_parent_count(np: node); |
27 | const char **parents; |
28 | const char *clk_name = node->name; |
29 | struct resource res; |
30 | struct clk_divider *div; |
31 | struct clk_gate *gate; |
32 | struct clk_mux *mux; |
33 | struct clk *clk; |
34 | void __iomem *reg; |
35 | int err; |
36 | |
37 | parents = kcalloc(n: num_parents, size: sizeof(*parents), GFP_KERNEL); |
38 | if (!parents) |
39 | return; |
40 | |
41 | reg = of_io_request_and_map(device: node, index: 0, name: of_node_full_name(np: node)); |
42 | if (IS_ERR(ptr: reg)) { |
43 | pr_err("Could not get registers for sun8i-mbus-clk\n" ); |
44 | goto err_free_parents; |
45 | } |
46 | |
47 | div = kzalloc(size: sizeof(*div), GFP_KERNEL); |
48 | if (!div) |
49 | goto err_unmap; |
50 | |
51 | mux = kzalloc(size: sizeof(*mux), GFP_KERNEL); |
52 | if (!mux) |
53 | goto err_free_div; |
54 | |
55 | gate = kzalloc(size: sizeof(*gate), GFP_KERNEL); |
56 | if (!gate) |
57 | goto err_free_mux; |
58 | |
59 | of_property_read_string(np: node, propname: "clock-output-names" , out_string: &clk_name); |
60 | of_clk_parent_fill(np: node, parents, size: num_parents); |
61 | |
62 | gate->reg = reg; |
63 | gate->bit_idx = SUN8I_MBUS_ENABLE; |
64 | gate->lock = &sun8i_a23_mbus_lock; |
65 | |
66 | div->reg = reg; |
67 | div->shift = SUN8I_MBUS_DIV_SHIFT; |
68 | div->width = SUN8I_MBUS_DIV_WIDTH; |
69 | div->lock = &sun8i_a23_mbus_lock; |
70 | |
71 | mux->reg = reg; |
72 | mux->shift = SUN8I_MBUS_MUX_SHIFT; |
73 | mux->mask = SUN8I_MBUS_MUX_MASK; |
74 | mux->lock = &sun8i_a23_mbus_lock; |
75 | |
76 | /* The MBUS clocks needs to be always enabled */ |
77 | clk = clk_register_composite(NULL, name: clk_name, parent_names: parents, num_parents, |
78 | mux_hw: &mux->hw, mux_ops: &clk_mux_ops, |
79 | rate_hw: &div->hw, rate_ops: &clk_divider_ops, |
80 | gate_hw: &gate->hw, gate_ops: &clk_gate_ops, |
81 | CLK_IS_CRITICAL); |
82 | if (IS_ERR(ptr: clk)) |
83 | goto err_free_gate; |
84 | |
85 | err = of_clk_add_provider(np: node, clk_src_get: of_clk_src_simple_get, data: clk); |
86 | if (err) |
87 | goto err_unregister_clk; |
88 | |
89 | kfree(objp: parents); /* parents is deep copied */ |
90 | |
91 | return; |
92 | |
93 | err_unregister_clk: |
94 | /* TODO: The composite clock stuff will leak a bit here. */ |
95 | clk_unregister(clk); |
96 | err_free_gate: |
97 | kfree(objp: gate); |
98 | err_free_mux: |
99 | kfree(objp: mux); |
100 | err_free_div: |
101 | kfree(objp: div); |
102 | err_unmap: |
103 | iounmap(addr: reg); |
104 | of_address_to_resource(dev: node, index: 0, r: &res); |
105 | release_mem_region(res.start, resource_size(&res)); |
106 | err_free_parents: |
107 | kfree(objp: parents); |
108 | } |
109 | CLK_OF_DECLARE(sun8i_a23_mbus, "allwinner,sun8i-a23-mbus-clk" , sun8i_a23_mbus_setup); |
110 | |