blob: 798fa9eb3ccaaca3eac8a2f73d84641d2441539e [file] [log] [blame]
Sergiu Moga68726cf2023-03-08 16:39:50 +02001// 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};