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