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}
159