1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Amlogic Meson SDHC clock controller |
4 | * |
5 | * Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/device.h> |
11 | #include <linux/platform_device.h> |
12 | |
13 | #include "meson-mx-sdhc.h" |
14 | |
15 | struct meson_mx_sdhc_clkc { |
16 | struct clk_mux src_sel; |
17 | struct clk_divider div; |
18 | struct clk_gate mod_clk_en; |
19 | struct clk_gate tx_clk_en; |
20 | struct clk_gate rx_clk_en; |
21 | struct clk_gate sd_clk_en; |
22 | }; |
23 | |
24 | static const struct clk_parent_data meson_mx_sdhc_src_sel_parents[4] = { |
25 | { .fw_name = "clkin0" }, |
26 | { .fw_name = "clkin1" }, |
27 | { .fw_name = "clkin2" }, |
28 | { .fw_name = "clkin3" }, |
29 | }; |
30 | |
31 | static const struct clk_div_table meson_mx_sdhc_div_table[] = { |
32 | { .div = 6, .val = 5, }, |
33 | { .div = 8, .val = 7, }, |
34 | { .div = 9, .val = 8, }, |
35 | { .div = 10, .val = 9, }, |
36 | { .div = 12, .val = 11, }, |
37 | { .div = 16, .val = 15, }, |
38 | { .div = 18, .val = 17, }, |
39 | { .div = 34, .val = 33, }, |
40 | { .div = 142, .val = 141, }, |
41 | { .div = 850, .val = 849, }, |
42 | { .div = 2126, .val = 2125, }, |
43 | { .div = 4096, .val = 4095, }, |
44 | { /* sentinel */ } |
45 | }; |
46 | |
47 | static int meson_mx_sdhc_clk_hw_register(struct device *dev, |
48 | const char *name_suffix, |
49 | const struct clk_parent_data *parents, |
50 | unsigned int num_parents, |
51 | const struct clk_ops *ops, |
52 | struct clk_hw *hw) |
53 | { |
54 | struct clk_init_data init = { }; |
55 | char clk_name[32]; |
56 | |
57 | snprintf(buf: clk_name, size: sizeof(clk_name), fmt: "%s#%s" , dev_name(dev), |
58 | name_suffix); |
59 | |
60 | init.name = clk_name; |
61 | init.ops = ops; |
62 | init.flags = CLK_SET_RATE_PARENT; |
63 | init.parent_data = parents; |
64 | init.num_parents = num_parents; |
65 | |
66 | hw->init = &init; |
67 | |
68 | return devm_clk_hw_register(dev, hw); |
69 | } |
70 | |
71 | static int meson_mx_sdhc_gate_clk_hw_register(struct device *dev, |
72 | const char *name_suffix, |
73 | struct clk_hw *parent, |
74 | struct clk_hw *hw, |
75 | struct clk_bulk_data *clk_bulk_data, |
76 | u8 bulk_index) |
77 | { |
78 | struct clk_parent_data parent_data = { .hw = parent }; |
79 | int ret; |
80 | |
81 | ret = meson_mx_sdhc_clk_hw_register(dev, name_suffix, parents: &parent_data, num_parents: 1, |
82 | ops: &clk_gate_ops, hw); |
83 | if (ret) |
84 | return ret; |
85 | |
86 | clk_bulk_data[bulk_index].clk = devm_clk_hw_get_clk(dev, hw, con_id: name_suffix); |
87 | if (IS_ERR(ptr: clk_bulk_data[bulk_index].clk)) |
88 | return PTR_ERR(ptr: clk_bulk_data[bulk_index].clk); |
89 | |
90 | return 0; |
91 | } |
92 | |
93 | int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base, |
94 | struct clk_bulk_data *clk_bulk_data) |
95 | { |
96 | struct clk_parent_data div_parent = { }; |
97 | struct meson_mx_sdhc_clkc *clkc_data; |
98 | int ret; |
99 | |
100 | clkc_data = devm_kzalloc(dev, size: sizeof(*clkc_data), GFP_KERNEL); |
101 | if (!clkc_data) |
102 | return -ENOMEM; |
103 | |
104 | clkc_data->src_sel.reg = base + MESON_SDHC_CLKC; |
105 | clkc_data->src_sel.mask = 0x3; |
106 | clkc_data->src_sel.shift = 16; |
107 | ret = meson_mx_sdhc_clk_hw_register(dev, name_suffix: "src_sel" , |
108 | parents: meson_mx_sdhc_src_sel_parents, num_parents: 4, |
109 | ops: &clk_mux_ops, |
110 | hw: &clkc_data->src_sel.hw); |
111 | if (ret) |
112 | return ret; |
113 | |
114 | clkc_data->div.reg = base + MESON_SDHC_CLKC; |
115 | clkc_data->div.shift = 0; |
116 | clkc_data->div.width = 12; |
117 | clkc_data->div.table = meson_mx_sdhc_div_table; |
118 | div_parent.hw = &clkc_data->src_sel.hw; |
119 | ret = meson_mx_sdhc_clk_hw_register(dev, name_suffix: "div" , parents: &div_parent, num_parents: 1, |
120 | ops: &clk_divider_ops, |
121 | hw: &clkc_data->div.hw); |
122 | if (ret) |
123 | return ret; |
124 | |
125 | clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC; |
126 | clkc_data->mod_clk_en.bit_idx = 15; |
127 | ret = meson_mx_sdhc_gate_clk_hw_register(dev, name_suffix: "mod_clk_on" , |
128 | parent: &clkc_data->div.hw, |
129 | hw: &clkc_data->mod_clk_en.hw, |
130 | clk_bulk_data, bulk_index: 0); |
131 | if (ret) |
132 | return ret; |
133 | |
134 | clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC; |
135 | clkc_data->tx_clk_en.bit_idx = 14; |
136 | ret = meson_mx_sdhc_gate_clk_hw_register(dev, name_suffix: "tx_clk_on" , |
137 | parent: &clkc_data->div.hw, |
138 | hw: &clkc_data->tx_clk_en.hw, |
139 | clk_bulk_data, bulk_index: 1); |
140 | if (ret) |
141 | return ret; |
142 | |
143 | clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC; |
144 | clkc_data->rx_clk_en.bit_idx = 13; |
145 | ret = meson_mx_sdhc_gate_clk_hw_register(dev, name_suffix: "rx_clk_on" , |
146 | parent: &clkc_data->div.hw, |
147 | hw: &clkc_data->rx_clk_en.hw, |
148 | clk_bulk_data, bulk_index: 2); |
149 | if (ret) |
150 | return ret; |
151 | |
152 | clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC; |
153 | clkc_data->sd_clk_en.bit_idx = 12; |
154 | ret = meson_mx_sdhc_gate_clk_hw_register(dev, name_suffix: "sd_clk_on" , |
155 | parent: &clkc_data->div.hw, |
156 | hw: &clkc_data->sd_clk_en.hw, |
157 | clk_bulk_data, bulk_index: 3); |
158 | return ret; |
159 | } |
160 | |