diff options
-rw-r--r-- | drivers/clk/at91/Kconfig | 7 | ||||
-rw-r--r-- | drivers/clk/at91/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/at91/clk-sam9x60-usb.c | 157 | ||||
-rw-r--r-- | drivers/clk/at91/pmc.h | 11 |
4 files changed, 176 insertions, 0 deletions
diff --git a/drivers/clk/at91/Kconfig b/drivers/clk/at91/Kconfig index 4abc8026b4..4563892647 100644 --- a/drivers/clk/at91/Kconfig +++ b/drivers/clk/at91/Kconfig @@ -61,3 +61,10 @@ config AT91_SAM9X60_PLL help This option is used to enable the AT91 SAM9X60's PLL clock driver. + +config AT91_SAM9X60_USB + bool "USB Clock support for SAM9X60 SoCs" + depends on CLK_AT91 + help + This option is used to enable the AT91 SAM9X60's USB clock + driver. diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile index 580b406d7b..e53dcb4ca7 100644 --- a/drivers/clk/at91/Makefile +++ b/drivers/clk/at91/Makefile @@ -9,6 +9,7 @@ obj-y += clk-peripheral.o obj-$(CONFIG_AT91_GENERIC_CLK) += clk-generic.o obj-$(CONFIG_AT91_UTMI) += clk-utmi.o obj-$(CONFIG_AT91_SAM9X60_PLL) += clk-sam9x60-pll.o +obj-$(CONFIG_AT91_SAM9X60_USB) += clk-sam9x60-usb.o obj-$(CONFIG_SAMA7G5) += sama7g5.o obj-$(CONFIG_SAM9X60) += sam9x60.o else diff --git a/drivers/clk/at91/clk-sam9x60-usb.c b/drivers/clk/at91/clk-sam9x60-usb.c new file mode 100644 index 0000000000..798fa9eb3c --- /dev/null +++ b/drivers/clk/at91/clk-sam9x60-usb.c @@ -0,0 +1,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, +}; diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 2b4dd9a3d9..17793b8802 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h @@ -71,6 +71,12 @@ struct clk_pcr_layout { u32 pid_mask; }; +struct clk_usbck_layout { + u32 offset; + u32 usbs_mask; + u32 usbdiv_mask; +}; + extern const struct clk_programmable_layout at91rm9200_programmable_layout; extern const struct clk_programmable_layout at91sam9g45_programmable_layout; extern const struct clk_programmable_layout at91sam9x5_programmable_layout; @@ -87,6 +93,11 @@ struct clk *at91_clk_sam9x5_main(void __iomem *reg, const char *name, const char * const *parent_names, int num_parents, const u32 *mux_table, int type); 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 clk * sam9x60_clk_register_div_pll(void __iomem *base, const char *name, const char *parent_name, u8 id, const struct clk_pll_characteristics *characteristics, |