summaryrefslogtreecommitdiff
path: root/drivers/clk/at91/clk-generated.c
blob: ac27d3e675039545d565923b472461fe104305fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
 * Copyright (C) 2016 Atmel Corporation
 *               Wenyou.Yang <wenyou.yang@atmel.com>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <clk-uclass.h>
#include <dm/device.h>
#include <linux/io.h>
#include <mach/at91_pmc.h>
#include "pmc.h"

DECLARE_GLOBAL_DATA_PTR;

#define GENERATED_SOURCE_MAX	6
#define GENERATED_MAX_DIV	255

/**
 * generated_clk_bind() - for the generated clock driver
 * Recursively bind its children as clk devices.
 *
 * @return: 0 on success, or negative error code on failure
 */
static int generated_clk_bind(struct udevice *dev)
{
	return at91_clk_sub_device_bind(dev, "generic-clk");
}

static const struct udevice_id generated_clk_match[] = {
	{ .compatible = "atmel,sama5d2-clk-generated" },
	{}
};

U_BOOT_DRIVER(generated_clk) = {
	.name = "generated-clk",
	.id = UCLASS_MISC,
	.of_match = generated_clk_match,
	.bind = generated_clk_bind,
};

/*-------------------------------------------------------------*/

struct generic_clk_priv {
	u32 num_parents;
};

static ulong generic_clk_get_rate(struct clk *clk)
{
	struct pmc_platdata *plat = dev_get_platdata(clk->dev);
	struct at91_pmc *pmc = plat->reg_base;
	struct clk parent;
	ulong clk_rate;
	u32 tmp, gckdiv;
	u8 parent_id;
	int ret;

	writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
	tmp = readl(&pmc->pcr);
	parent_id = (tmp >> AT91_PMC_PCR_GCKCSS_OFFSET) &
		    AT91_PMC_PCR_GCKCSS_MASK;
	gckdiv = (tmp >> AT91_PMC_PCR_GCKDIV_OFFSET) & AT91_PMC_PCR_GCKDIV_MASK;

	ret = clk_get_by_index(dev_get_parent(clk->dev), parent_id, &parent);
	if (ret)
		return 0;

	clk_rate = clk_get_rate(&parent) / (gckdiv + 1);

	clk_free(&parent);

	return clk_rate;
}

static ulong generic_clk_set_rate(struct clk *clk, ulong rate)
{
	struct pmc_platdata *plat = dev_get_platdata(clk->dev);
	struct at91_pmc *pmc = plat->reg_base;
	struct generic_clk_priv *priv = dev_get_priv(clk->dev);
	struct clk parent, best_parent;
	ulong tmp_rate, best_rate = rate, parent_rate;
	int tmp_diff, best_diff = -1;
	u32 div, best_div = 0;
	u8 best_parent_id = 0;
	u8 i;
	u32 tmp;
	int ret;

	for (i = 0; i < priv->num_parents; i++) {
		ret = clk_get_by_index(dev_get_parent(clk->dev), i, &parent);
		if (ret)
			return ret;

		parent_rate = clk_get_rate(&parent);
		if (IS_ERR_VALUE(parent_rate))
			return parent_rate;

		for (div = 1; div < GENERATED_MAX_DIV + 2; div++) {
			tmp_rate = DIV_ROUND_CLOSEST(parent_rate, div);
			if (rate < tmp_rate)
				continue;
			tmp_diff = rate - tmp_rate;

			if (best_diff < 0 || best_diff > tmp_diff) {
				best_rate = tmp_rate;
				best_diff = tmp_diff;

				best_div = div - 1;
				best_parent = parent;
				best_parent_id = i;
			}

			if (!best_diff || tmp_rate < rate)
				break;
		}

		if (!best_diff)
			break;
	}

	debug("GCK: best parent: %s, best_rate = %ld, best_div = %d\n",
	      best_parent.dev->name, best_rate, best_div);

	ret = clk_enable(&best_parent);
	if (ret)
		return ret;

	writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
	tmp = readl(&pmc->pcr);
	tmp &= ~(AT91_PMC_PCR_GCKDIV | AT91_PMC_PCR_GCKCSS);
	tmp |= AT91_PMC_PCR_GCKCSS_(best_parent_id) |
	       AT91_PMC_PCR_CMD_WRITE |
	       AT91_PMC_PCR_GCKDIV_(best_div) |
	       AT91_PMC_PCR_GCKEN;
	writel(tmp, &pmc->pcr);

	while (!(readl(&pmc->sr) & AT91_PMC_GCKRDY))
		;

	return 0;
}

static struct clk_ops generic_clk_ops = {
	.of_xlate = at91_clk_of_xlate,
	.get_rate = generic_clk_get_rate,
	.set_rate = generic_clk_set_rate,
};

static int generic_clk_ofdata_to_platdata(struct udevice *dev)
{
	struct generic_clk_priv *priv = dev_get_priv(dev);
	u32 cells[GENERATED_SOURCE_MAX];
	u32 num_parents;

	num_parents = fdtdec_get_int_array_count(gd->fdt_blob,
			dev_of_offset(dev_get_parent(dev)), "clocks", cells,
			GENERATED_SOURCE_MAX);

	if (!num_parents)
		return -1;

	priv->num_parents = num_parents;

	return 0;
}

U_BOOT_DRIVER(generic_clk) = {
	.name = "generic-clk",
	.id = UCLASS_CLK,
	.probe = at91_clk_probe,
	.ofdata_to_platdata = generic_clk_ofdata_to_platdata,
	.priv_auto_alloc_size = sizeof(struct generic_clk_priv),
	.platdata_auto_alloc_size = sizeof(struct pmc_platdata),
	.ops = &generic_clk_ops,
};