// SPDX-License-Identifier: GPL-2.0+ /* * SAM9X60's USB Clock support. * * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries * * Author: Sergiu Moga */ #include #include #include #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, };