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