1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * SAM9X60's USB Clock support.
4 *
5 * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
6 *
7 * Author: Sergiu Moga <sergiu.moga@microchip.com>
8 */
9
10#include <clk-uclass.h>
11#include <dm.h>
12#include <linux/clk-provider.h>
13
14#include "pmc.h"
15
16#define UBOOT_DM_CLK_AT91_SAM9X60_USB		"at91-sam9x60-usb-clk"
17
18struct sam9x60_usb {
19	const struct clk_usbck_layout		*layout;
20	void					__iomem *base;
21	struct clk				clk;
22	const u32				*clk_mux_table;
23	const u32				*mux_table;
24	const char * const			*parent_names;
25	u32					num_parents;
26	u8					id;
27};
28
29#define to_sam9x60_usb(_clk)	container_of(_clk, struct sam9x60_usb, clk)
30#define USB_MAX_DIV		15
31
32static int sam9x60_usb_clk_set_parent(struct clk *clk, struct clk *parent)
33{
34	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
35	int index;
36	u32 val;
37
38	index = at91_clk_mux_val_to_index(usb->clk_mux_table, usb->num_parents,
39					  parent->id);
40	if (index < 0)
41		return index;
42
43	index = at91_clk_mux_index_to_val(usb->mux_table, usb->num_parents,
44					  index);
45	if (index < 0)
46		return index;
47
48	pmc_read(usb->base, usb->layout->offset, &val);
49	val &= ~usb->layout->usbs_mask;
50	val |= index << (ffs(usb->layout->usbs_mask - 1));
51	pmc_write(usb->base, usb->layout->offset, val);
52
53	return 0;
54}
55
56static ulong sam9x60_usb_clk_get_rate(struct clk *clk)
57{
58	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
59	ulong parent_rate = clk_get_parent_rate(clk);
60	u32 val, usbdiv;
61
62	if (!parent_rate)
63		return 0;
64
65	pmc_read(usb->base, usb->layout->offset, &val);
66	usbdiv = (val & usb->layout->usbdiv_mask) >>
67		(ffs(usb->layout->usbdiv_mask) - 1);
68	return parent_rate / (usbdiv + 1);
69}
70
71static ulong sam9x60_usb_clk_set_rate(struct clk *clk, ulong rate)
72{
73	struct sam9x60_usb *usb = to_sam9x60_usb(clk);
74	ulong parent_rate = clk_get_parent_rate(clk);
75	u32 usbdiv, val;
76
77	if (!parent_rate)
78		return 0;
79
80	usbdiv = DIV_ROUND_CLOSEST(parent_rate, rate);
81	if (usbdiv > USB_MAX_DIV + 1 || !usbdiv)
82		return 0;
83
84	pmc_read(usb->base, usb->layout->offset, &val);
85	val &= usb->layout->usbdiv_mask;
86	val |= (usbdiv - 1) << (ffs(usb->layout->usbdiv_mask) - 1);
87	pmc_write(usb->base, usb->layout->offset, val);
88
89	return parent_rate / usbdiv;
90}
91
92static const struct clk_ops sam9x60_usb_ops = {
93	.set_parent = sam9x60_usb_clk_set_parent,
94	.set_rate = sam9x60_usb_clk_set_rate,
95	.get_rate = sam9x60_usb_clk_get_rate,
96};
97
98struct clk *
99sam9x60_clk_register_usb(void __iomem *base,  const char *name,
100			 const char * const *parent_names, u8 num_parents,
101			 const struct clk_usbck_layout *usbck_layout,
102			 const u32 *clk_mux_table, const u32 *mux_table, u8 id)
103{
104	struct sam9x60_usb *usb;
105	struct clk *clk;
106	int ret, index;
107	u32 val;
108
109	if (!base || !name || !parent_names || !num_parents ||
110	    !clk_mux_table || !mux_table)
111		return ERR_PTR(-EINVAL);
112
113	usb = kzalloc(sizeof(*usb), GFP_KERNEL);
114	if (!usb)
115		return ERR_PTR(-ENOMEM);
116
117	usb->id = id;
118	usb->base = base;
119	usb->layout = usbck_layout;
120	usb->parent_names = parent_names;
121	usb->num_parents = num_parents;
122	usb->clk_mux_table = clk_mux_table;
123	usb->mux_table = mux_table;
124
125	clk = &usb->clk;
126	clk->flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
127		     CLK_SET_RATE_PARENT;
128
129	pmc_read(usb->base, usb->layout->offset, &val);
130
131	val = (val & usb->layout->usbs_mask) >>
132		(ffs(usb->layout->usbs_mask) - 1);
133
134	index = at91_clk_mux_val_to_index(usb->mux_table, usb->num_parents,
135					  val);
136
137	if (index < 0) {
138		kfree(usb);
139		return ERR_PTR(index);
140	}
141
142	ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X60_USB, name,
143			   parent_names[index]);
144	if (ret) {
145		kfree(usb);
146		clk = ERR_PTR(ret);
147	}
148
149	return clk;
150}
151
152U_BOOT_DRIVER(at91_sam9x60_usb_clk) = {
153	.name = UBOOT_DM_CLK_AT91_SAM9X60_USB,
154	.id = UCLASS_CLK,
155	.ops = &sam9x60_usb_ops,
156	.flags = DM_FLAG_PRE_RELOC,
157};
158