summaryrefslogtreecommitdiff
path: root/drivers/clk/at91/clk-generated.c
blob: f6164cc8ca1c8ac1b5430502710d911bc832e3f6 (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
/*
 * 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

struct generated_clk_priv {
	u32 num_parents;
};

static ulong generated_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;
	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(clk->dev, parent_id, &parent);
	if (ret)
		return 0;

	return clk_get_rate(&parent) / (gckdiv + 1);
}

static ulong generated_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 generated_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(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 generated_clk_ops = {
	.get_rate = generated_clk_get_rate,
	.set_rate = generated_clk_set_rate,
};

static int generated_clk_ofdata_to_platdata(struct udevice *dev)
{
	struct generated_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,
						 "clocks", cells,
						 GENERATED_SOURCE_MAX);

	if (!num_parents)
		return -1;

	priv->num_parents = num_parents;

	return 0;
}

static int generated_clk_bind(struct udevice *dev)
{
	return at91_pmc_clk_node_bind(dev);
}

static int generated_clk_probe(struct udevice *dev)
{
	return at91_pmc_core_probe(dev);
}

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

U_BOOT_DRIVER(generated_clk) = {
	.name = "generated-clk",
	.id = UCLASS_CLK,
	.of_match = generated_clk_match,
	.bind = generated_clk_bind,
	.probe = generated_clk_probe,
	.ofdata_to_platdata = generated_clk_ofdata_to_platdata,
	.priv_auto_alloc_size = sizeof(struct generated_clk_priv),
	.platdata_auto_alloc_size = sizeof(struct pmc_platdata),
	.ops = &generated_clk_ops,
};