xref: /linux/drivers/mmc/host/meson-mx-sdhc-clkc.c (revision 55a42f78ffd386e01a5404419f8c5ded7db70a21)
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(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 
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, &parent_data, 1,
82 					    &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, name_suffix);
87 
88 	return PTR_ERR_OR_ZERO(clk_bulk_data[bulk_index].clk);
89 }
90 
91 int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base,
92 				struct clk_bulk_data *clk_bulk_data)
93 {
94 	struct clk_parent_data div_parent = { };
95 	struct meson_mx_sdhc_clkc *clkc_data;
96 	int ret;
97 
98 	clkc_data = devm_kzalloc(dev, sizeof(*clkc_data), GFP_KERNEL);
99 	if (!clkc_data)
100 		return -ENOMEM;
101 
102 	clkc_data->src_sel.reg = base + MESON_SDHC_CLKC;
103 	clkc_data->src_sel.mask = 0x3;
104 	clkc_data->src_sel.shift = 16;
105 	ret = meson_mx_sdhc_clk_hw_register(dev, "src_sel",
106 					    meson_mx_sdhc_src_sel_parents, 4,
107 					    &clk_mux_ops,
108 					    &clkc_data->src_sel.hw);
109 	if (ret)
110 		return ret;
111 
112 	clkc_data->div.reg = base + MESON_SDHC_CLKC;
113 	clkc_data->div.shift = 0;
114 	clkc_data->div.width = 12;
115 	clkc_data->div.table = meson_mx_sdhc_div_table;
116 	div_parent.hw = &clkc_data->src_sel.hw;
117 	ret = meson_mx_sdhc_clk_hw_register(dev, "div", &div_parent, 1,
118 					    &clk_divider_ops,
119 					    &clkc_data->div.hw);
120 	if (ret)
121 		return ret;
122 
123 	clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC;
124 	clkc_data->mod_clk_en.bit_idx = 15;
125 	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "mod_clk_on",
126 						 &clkc_data->div.hw,
127 						 &clkc_data->mod_clk_en.hw,
128 						 clk_bulk_data, 0);
129 	if (ret)
130 		return ret;
131 
132 	clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC;
133 	clkc_data->tx_clk_en.bit_idx = 14;
134 	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "tx_clk_on",
135 						 &clkc_data->div.hw,
136 						 &clkc_data->tx_clk_en.hw,
137 						 clk_bulk_data, 1);
138 	if (ret)
139 		return ret;
140 
141 	clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC;
142 	clkc_data->rx_clk_en.bit_idx = 13;
143 	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "rx_clk_on",
144 						 &clkc_data->div.hw,
145 						 &clkc_data->rx_clk_en.hw,
146 						 clk_bulk_data, 2);
147 	if (ret)
148 		return ret;
149 
150 	clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC;
151 	clkc_data->sd_clk_en.bit_idx = 12;
152 	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "sd_clk_on",
153 						 &clkc_data->div.hw,
154 						 &clkc_data->sd_clk_en.hw,
155 						 clk_bulk_data, 3);
156 	return ret;
157 }
158