~funderscore blog cgit wiki get in touch
aboutsummaryrefslogtreecommitdiff
blob: 798fa9eb3ccaaca3eac8a2f73d84641d2441539e (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * SAM9X60's USB Clock support.
 *
 * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
 *
 * Author: Sergiu Moga <sergiu.moga@microchip.com>
 */

#include <clk-uclass.h>
#include <dm.h>
#include <linux/clk-provider.h>

#include "pmc.h"

#define UBOOT_DM_CLK_AT91_SAM9X60_USB		"at91-sam9x60-usb-clk"

struct sam9x60_usb {
	const struct clk_usbck_layout		*layout;
	void					__iomem *base;
	struct clk				clk;
	const u32				*clk_mux_table;
	const u32				*mux_table;
	const char * const			*parent_names;
	u32					num_parents;
	u8					id;
};

#define to_sam9x60_usb(_clk)	container_of(_clk, struct sam9x60_usb, clk)
#define USB_MAX_DIV		15

static int sam9x60_usb_clk_set_parent(struct clk *clk, struct clk *parent)
{
	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
	int index;
	u32 val;

	index = at91_clk_mux_val_to_index(usb->clk_mux_table, usb->num_parents,
					  parent->id);
	if (index < 0)
		return index;

	index = at91_clk_mux_index_to_val(usb->mux_table, usb->num_parents,
					  index);
	if (index < 0)
		return index;

	pmc_read(usb->base, usb->layout->offset, &val);
	val &= ~usb->layout->usbs_mask;
	val |= index << (ffs(usb->layout->usbs_mask - 1));
	pmc_write(usb->base, usb->layout->offset, val);

	return 0;
}

static ulong sam9x60_usb_clk_get_rate(struct clk *clk)
{
	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
	ulong parent_rate = clk_get_parent_rate(clk);
	u32 val, usbdiv;

	if (!parent_rate)
		return 0;

	pmc_read(usb->base, usb->layout->offset, &val);
	usbdiv = (val & usb->layout->usbdiv_mask) >>
		(ffs(usb->layout->usbdiv_mask) - 1);
	return parent_rate / (usbdiv + 1);
}

static ulong sam9x60_usb_clk_set_rate(struct clk *clk, ulong rate)
{
	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
	ulong parent_rate = clk_get_parent_rate(clk);
	u32 usbdiv, val;

	if (!parent_rate)
		return 0;

	usbdiv = DIV_ROUND_CLOSEST(parent_rate, rate);
	if (usbdiv > USB_MAX_DIV + 1 || !usbdiv)
		return 0;

	pmc_read(usb->base, usb->layout->offset, &val);
	val &= usb->layout->usbdiv_mask;
	val |= (usbdiv - 1) << (ffs(usb->layout->usbdiv_mask) - 1);
	pmc_write(usb->base, usb->layout->offset, val);

	return parent_rate / usbdiv;
}

static const struct clk_ops sam9x60_usb_ops = {
	.set_parent = sam9x60_usb_clk_set_parent,
	.set_rate = sam9x60_usb_clk_set_rate,
	.get_rate = sam9x60_usb_clk_get_rate,
};

struct clk *
sam9x60_clk_register_usb(void __iomem *base,  const char *name,
			 const char * const *parent_names, u8 num_parents,
			 const struct clk_usbck_layout *usbck_layout,
			 const u32 *clk_mux_table, const u32 *mux_table, u8 id)
{
	struct sam9x60_usb *usb;
	struct clk *clk;
	int ret, index;
	u32 val;

	if (!base || !name || !parent_names || !num_parents ||
	    !clk_mux_table || !mux_table)
		return ERR_PTR(-EINVAL);

	usb = kzalloc(sizeof(*usb), GFP_KERNEL);
	if (!usb)
		return ERR_PTR(-ENOMEM);

	usb->id = id;
	usb->base = base;
	usb->layout = usbck_layout;
	usb->parent_names = parent_names;
	usb->num_parents = num_parents;
	usb->clk_mux_table = clk_mux_table;
	usb->mux_table = mux_table;

	clk = &usb->clk;
	clk->flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
		     CLK_SET_RATE_PARENT;

	pmc_read(usb->base, usb->layout->offset, &val);

	val = (val & usb->layout->usbs_mask) >>
		(ffs(usb->layout->usbs_mask) - 1);

	index = at91_clk_mux_val_to_index(usb->mux_table, usb->num_parents,
					  val);

	if (index < 0) {
		kfree(usb);
		return ERR_PTR(index);
	}

	ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X60_USB, name,
			   parent_names[index]);
	if (ret) {
		kfree(usb);
		clk = ERR_PTR(ret);
	}

	return clk;
}

U_BOOT_DRIVER(at91_sam9x60_usb_clk) = {
	.name = UBOOT_DM_CLK_AT91_SAM9X60_USB,
	.id = UCLASS_CLK,
	.ops = &sam9x60_usb_ops,
	.flags = DM_FLAG_PRE_RELOC,
};