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(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
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 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
93int 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

source code of linux/drivers/mmc/host/meson-mx-sdhc-clkc.c