1/*
2 * Copyright 2015 Chen-Yu Tsai
3 *
4 * Chen-Yu Tsai	<wens@csie.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/clk-provider.h>
18#include <linux/clkdev.h>
19#include <linux/module.h>
20#include <linux/of.h>
21#include <linux/of_device.h>
22#include <linux/reset.h>
23#include <linux/platform_device.h>
24#include <linux/reset-controller.h>
25#include <linux/spinlock.h>
26
27#define SUN9I_MMC_WIDTH		4
28
29#define SUN9I_MMC_GATE_BIT	16
30#define SUN9I_MMC_RESET_BIT	18
31
32struct sun9i_mmc_clk_data {
33	spinlock_t			lock;
34	void __iomem			*membase;
35	struct clk			*clk;
36	struct reset_control		*reset;
37	struct clk_onecell_data		clk_data;
38	struct reset_controller_dev	rcdev;
39};
40
41static int sun9i_mmc_reset_assert(struct reset_controller_dev *rcdev,
42			      unsigned long id)
43{
44	struct sun9i_mmc_clk_data *data = container_of(rcdev,
45						       struct sun9i_mmc_clk_data,
46						       rcdev);
47	unsigned long flags;
48	void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id;
49	u32 val;
50
51	clk_prepare_enable(data->clk);
52	spin_lock_irqsave(&data->lock, flags);
53
54	val = readl(reg);
55	writel(val & ~BIT(SUN9I_MMC_RESET_BIT), reg);
56
57	spin_unlock_irqrestore(&data->lock, flags);
58	clk_disable_unprepare(data->clk);
59
60	return 0;
61}
62
63static int sun9i_mmc_reset_deassert(struct reset_controller_dev *rcdev,
64				unsigned long id)
65{
66	struct sun9i_mmc_clk_data *data = container_of(rcdev,
67						       struct sun9i_mmc_clk_data,
68						       rcdev);
69	unsigned long flags;
70	void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id;
71	u32 val;
72
73	clk_prepare_enable(data->clk);
74	spin_lock_irqsave(&data->lock, flags);
75
76	val = readl(reg);
77	writel(val | BIT(SUN9I_MMC_RESET_BIT), reg);
78
79	spin_unlock_irqrestore(&data->lock, flags);
80	clk_disable_unprepare(data->clk);
81
82	return 0;
83}
84
85static struct reset_control_ops sun9i_mmc_reset_ops = {
86	.assert		= sun9i_mmc_reset_assert,
87	.deassert	= sun9i_mmc_reset_deassert,
88};
89
90static int sun9i_a80_mmc_config_clk_probe(struct platform_device *pdev)
91{
92	struct device_node *np = pdev->dev.of_node;
93	struct sun9i_mmc_clk_data *data;
94	struct clk_onecell_data *clk_data;
95	const char *clk_name = np->name;
96	const char *clk_parent;
97	struct resource *r;
98	int count, i, ret;
99
100	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
101	if (!data)
102		return -ENOMEM;
103
104	spin_lock_init(&data->lock);
105
106	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
107	/* one clock/reset pair per word */
108	count = DIV_ROUND_UP((r->end - r->start + 1), SUN9I_MMC_WIDTH);
109	data->membase = devm_ioremap_resource(&pdev->dev, r);
110	if (IS_ERR(data->membase))
111		return PTR_ERR(data->membase);
112
113	clk_data = &data->clk_data;
114	clk_data->clk_num = count;
115	clk_data->clks = devm_kcalloc(&pdev->dev, count, sizeof(struct clk *),
116				      GFP_KERNEL);
117	if (!clk_data->clks)
118		return -ENOMEM;
119
120	data->clk = devm_clk_get(&pdev->dev, NULL);
121	if (IS_ERR(data->clk)) {
122		dev_err(&pdev->dev, "Could not get clock\n");
123		return PTR_ERR(data->clk);
124	}
125
126	data->reset = devm_reset_control_get(&pdev->dev, NULL);
127	if (IS_ERR(data->reset)) {
128		dev_err(&pdev->dev, "Could not get reset control\n");
129		return PTR_ERR(data->reset);
130	}
131
132	ret = reset_control_deassert(data->reset);
133	if (ret) {
134		dev_err(&pdev->dev, "Reset deassert err %d\n", ret);
135		return ret;
136	}
137
138	clk_parent = __clk_get_name(data->clk);
139	for (i = 0; i < count; i++) {
140		of_property_read_string_index(np, "clock-output-names",
141					      i, &clk_name);
142
143		clk_data->clks[i] = clk_register_gate(&pdev->dev, clk_name,
144						      clk_parent, 0,
145						      data->membase + SUN9I_MMC_WIDTH * i,
146						      SUN9I_MMC_GATE_BIT, 0,
147						      &data->lock);
148
149		if (IS_ERR(clk_data->clks[i])) {
150			ret = PTR_ERR(clk_data->clks[i]);
151			goto err_clk_register;
152		}
153	}
154
155	ret = of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
156	if (ret)
157		goto err_clk_provider;
158
159	data->rcdev.owner = THIS_MODULE;
160	data->rcdev.nr_resets = count;
161	data->rcdev.ops = &sun9i_mmc_reset_ops;
162	data->rcdev.of_node = pdev->dev.of_node;
163
164	ret = reset_controller_register(&data->rcdev);
165	if (ret)
166		goto err_rc_reg;
167
168	platform_set_drvdata(pdev, data);
169
170	return 0;
171
172err_rc_reg:
173	of_clk_del_provider(np);
174
175err_clk_provider:
176	for (i = 0; i < count; i++)
177		clk_unregister(clk_data->clks[i]);
178
179err_clk_register:
180	reset_control_assert(data->reset);
181
182	return ret;
183}
184
185static int sun9i_a80_mmc_config_clk_remove(struct platform_device *pdev)
186{
187	struct device_node *np = pdev->dev.of_node;
188	struct sun9i_mmc_clk_data *data = platform_get_drvdata(pdev);
189	struct clk_onecell_data *clk_data = &data->clk_data;
190	int i;
191
192	reset_controller_unregister(&data->rcdev);
193	of_clk_del_provider(np);
194	for (i = 0; i < clk_data->clk_num; i++)
195		clk_unregister(clk_data->clks[i]);
196
197	reset_control_assert(data->reset);
198
199	return 0;
200}
201
202static const struct of_device_id sun9i_a80_mmc_config_clk_dt_ids[] = {
203	{ .compatible = "allwinner,sun9i-a80-mmc-config-clk" },
204	{ /* sentinel */ }
205};
206
207static struct platform_driver sun9i_a80_mmc_config_clk_driver = {
208	.driver = {
209		.name = "sun9i-a80-mmc-config-clk",
210		.of_match_table = sun9i_a80_mmc_config_clk_dt_ids,
211	},
212	.probe = sun9i_a80_mmc_config_clk_probe,
213	.remove = sun9i_a80_mmc_config_clk_remove,
214};
215module_platform_driver(sun9i_a80_mmc_config_clk_driver);
216
217MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
218MODULE_DESCRIPTION("Allwinner A80 MMC clock/reset Driver");
219MODULE_LICENSE("GPL v2");
220