1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2015 Chen-Yu Tsai |
4 | * |
5 | * Chen-Yu Tsai <wens@csie.org> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/init.h> |
12 | #include <linux/io.h> |
13 | #include <linux/of.h> |
14 | #include <linux/reset.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/reset-controller.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/spinlock.h> |
19 | |
20 | #define SUN9I_MMC_WIDTH 4 |
21 | |
22 | #define SUN9I_MMC_GATE_BIT 16 |
23 | #define SUN9I_MMC_RESET_BIT 18 |
24 | |
25 | struct sun9i_mmc_clk_data { |
26 | spinlock_t lock; |
27 | void __iomem *membase; |
28 | struct clk *clk; |
29 | struct reset_control *reset; |
30 | struct clk_onecell_data clk_data; |
31 | struct reset_controller_dev rcdev; |
32 | }; |
33 | |
34 | static int sun9i_mmc_reset_assert(struct reset_controller_dev *rcdev, |
35 | unsigned long id) |
36 | { |
37 | struct sun9i_mmc_clk_data *data = container_of(rcdev, |
38 | struct sun9i_mmc_clk_data, |
39 | rcdev); |
40 | unsigned long flags; |
41 | void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; |
42 | u32 val; |
43 | |
44 | clk_prepare_enable(clk: data->clk); |
45 | spin_lock_irqsave(&data->lock, flags); |
46 | |
47 | val = readl(addr: reg); |
48 | writel(val: val & ~BIT(SUN9I_MMC_RESET_BIT), addr: reg); |
49 | |
50 | spin_unlock_irqrestore(lock: &data->lock, flags); |
51 | clk_disable_unprepare(clk: data->clk); |
52 | |
53 | return 0; |
54 | } |
55 | |
56 | static int sun9i_mmc_reset_deassert(struct reset_controller_dev *rcdev, |
57 | unsigned long id) |
58 | { |
59 | struct sun9i_mmc_clk_data *data = container_of(rcdev, |
60 | struct sun9i_mmc_clk_data, |
61 | rcdev); |
62 | unsigned long flags; |
63 | void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; |
64 | u32 val; |
65 | |
66 | clk_prepare_enable(clk: data->clk); |
67 | spin_lock_irqsave(&data->lock, flags); |
68 | |
69 | val = readl(addr: reg); |
70 | writel(val: val | BIT(SUN9I_MMC_RESET_BIT), addr: reg); |
71 | |
72 | spin_unlock_irqrestore(lock: &data->lock, flags); |
73 | clk_disable_unprepare(clk: data->clk); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static int sun9i_mmc_reset_reset(struct reset_controller_dev *rcdev, |
79 | unsigned long id) |
80 | { |
81 | sun9i_mmc_reset_assert(rcdev, id); |
82 | udelay(10); |
83 | sun9i_mmc_reset_deassert(rcdev, id); |
84 | |
85 | return 0; |
86 | } |
87 | |
88 | static const struct reset_control_ops sun9i_mmc_reset_ops = { |
89 | .assert = sun9i_mmc_reset_assert, |
90 | .deassert = sun9i_mmc_reset_deassert, |
91 | .reset = sun9i_mmc_reset_reset, |
92 | }; |
93 | |
94 | static int sun9i_a80_mmc_config_clk_probe(struct platform_device *pdev) |
95 | { |
96 | struct device_node *np = pdev->dev.of_node; |
97 | struct sun9i_mmc_clk_data *data; |
98 | struct clk_onecell_data *clk_data; |
99 | const char *clk_name = np->name; |
100 | const char *clk_parent; |
101 | struct resource *r; |
102 | int count, i, ret; |
103 | |
104 | data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*data), GFP_KERNEL); |
105 | if (!data) |
106 | return -ENOMEM; |
107 | |
108 | spin_lock_init(&data->lock); |
109 | |
110 | data->membase = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &r); |
111 | if (IS_ERR(ptr: data->membase)) |
112 | return PTR_ERR(ptr: data->membase); |
113 | |
114 | /* one clock/reset pair per word */ |
115 | count = DIV_ROUND_UP((resource_size(r)), SUN9I_MMC_WIDTH); |
116 | |
117 | clk_data = &data->clk_data; |
118 | clk_data->clk_num = count; |
119 | clk_data->clks = devm_kcalloc(dev: &pdev->dev, n: count, size: sizeof(struct clk *), |
120 | GFP_KERNEL); |
121 | if (!clk_data->clks) |
122 | return -ENOMEM; |
123 | |
124 | data->clk = devm_clk_get(dev: &pdev->dev, NULL); |
125 | if (IS_ERR(ptr: data->clk)) { |
126 | dev_err(&pdev->dev, "Could not get clock\n" ); |
127 | return PTR_ERR(ptr: data->clk); |
128 | } |
129 | |
130 | data->reset = devm_reset_control_get_exclusive(dev: &pdev->dev, NULL); |
131 | if (IS_ERR(ptr: data->reset)) { |
132 | dev_err(&pdev->dev, "Could not get reset control\n" ); |
133 | return PTR_ERR(ptr: data->reset); |
134 | } |
135 | |
136 | ret = reset_control_deassert(rstc: data->reset); |
137 | if (ret) { |
138 | dev_err(&pdev->dev, "Reset deassert err %d\n" , ret); |
139 | return ret; |
140 | } |
141 | |
142 | clk_parent = __clk_get_name(clk: data->clk); |
143 | for (i = 0; i < count; i++) { |
144 | of_property_read_string_index(np, propname: "clock-output-names" , |
145 | index: i, output: &clk_name); |
146 | |
147 | clk_data->clks[i] = clk_register_gate(dev: &pdev->dev, name: clk_name, |
148 | parent_name: clk_parent, flags: 0, |
149 | reg: data->membase + SUN9I_MMC_WIDTH * i, |
150 | SUN9I_MMC_GATE_BIT, clk_gate_flags: 0, |
151 | lock: &data->lock); |
152 | |
153 | if (IS_ERR(ptr: clk_data->clks[i])) { |
154 | ret = PTR_ERR(ptr: clk_data->clks[i]); |
155 | goto err_clk_register; |
156 | } |
157 | } |
158 | |
159 | ret = of_clk_add_provider(np, clk_src_get: of_clk_src_onecell_get, data: clk_data); |
160 | if (ret) |
161 | goto err_clk_provider; |
162 | |
163 | data->rcdev.owner = THIS_MODULE; |
164 | data->rcdev.nr_resets = count; |
165 | data->rcdev.ops = &sun9i_mmc_reset_ops; |
166 | data->rcdev.of_node = pdev->dev.of_node; |
167 | |
168 | ret = reset_controller_register(rcdev: &data->rcdev); |
169 | if (ret) |
170 | goto err_rc_reg; |
171 | |
172 | platform_set_drvdata(pdev, data); |
173 | |
174 | return 0; |
175 | |
176 | err_rc_reg: |
177 | of_clk_del_provider(np); |
178 | |
179 | err_clk_provider: |
180 | for (i = 0; i < count; i++) |
181 | clk_unregister(clk: clk_data->clks[i]); |
182 | |
183 | err_clk_register: |
184 | reset_control_assert(rstc: data->reset); |
185 | |
186 | return ret; |
187 | } |
188 | |
189 | static const struct of_device_id sun9i_a80_mmc_config_clk_dt_ids[] = { |
190 | { .compatible = "allwinner,sun9i-a80-mmc-config-clk" }, |
191 | { /* sentinel */ } |
192 | }; |
193 | |
194 | static struct platform_driver sun9i_a80_mmc_config_clk_driver = { |
195 | .driver = { |
196 | .name = "sun9i-a80-mmc-config-clk" , |
197 | .suppress_bind_attrs = true, |
198 | .of_match_table = sun9i_a80_mmc_config_clk_dt_ids, |
199 | }, |
200 | .probe = sun9i_a80_mmc_config_clk_probe, |
201 | }; |
202 | builtin_platform_driver(sun9i_a80_mmc_config_clk_driver); |
203 | |