// SPDX-License-Identifier: GPL-2.0+ /* * UTMI clock support for AT91 architectures. * * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries * * Author: Claudiu Beznea * * Based on drivers/clk/at91/clk-utmi.c from Linux. */ #include #include #include #include #include #include #include #include #include #include "pmc.h" #define UBOOT_DM_CLK_AT91_UTMI "at91-utmi-clk" #define UBOOT_DM_CLK_AT91_SAMA7G5_UTMI "at91-sama7g5-utmi-clk" /* * The purpose of this clock is to generate a 480 MHz signal. A different * rate can't be configured. */ #define UTMI_RATE 480000000 struct clk_utmi { void __iomem *base; struct regmap *regmap_sfr; struct clk clk; }; #define to_clk_utmi(_clk) container_of(_clk, struct clk_utmi, clk) static inline bool clk_utmi_ready(struct regmap *regmap) { unsigned int status; pmc_read(regmap, AT91_PMC_SR, &status); return !!(status & AT91_PMC_LOCKU); } static int clk_utmi_enable(struct clk *clk) { struct clk_utmi *utmi = to_clk_utmi(clk); unsigned int uckr = AT91_PMC_UPLLEN | AT91_PMC_UPLLCOUNT | AT91_PMC_BIASEN; unsigned int utmi_ref_clk_freq; ulong parent_rate = clk_get_parent_rate(clk); /* * If mainck rate is different from 12 MHz, we have to configure the * FREQ field of the SFR_UTMICKTRIM register to generate properly * the utmi clock. */ switch (parent_rate) { case 12000000: utmi_ref_clk_freq = 0; break; case 16000000: utmi_ref_clk_freq = 1; break; case 24000000: utmi_ref_clk_freq = 2; break; /* * Not supported on SAMA5D2 but it's not an issue since MAINCK * maximum value is 24 MHz. */ case 48000000: utmi_ref_clk_freq = 3; break; default: debug("UTMICK: unsupported mainck rate\n"); return -EINVAL; } if (utmi->regmap_sfr) { regmap_update_bits(utmi->regmap_sfr, AT91_SFR_UTMICKTRIM, AT91_UTMICKTRIM_FREQ, utmi_ref_clk_freq); } else if (utmi_ref_clk_freq) { debug("UTMICK: sfr node required\n"); return -EINVAL; } pmc_update_bits(utmi->base, AT91_CKGR_UCKR, uckr, uckr); while (!clk_utmi_ready(utmi->base)) { debug("waiting for utmi...\n"); cpu_relax(); } return 0; } static int clk_utmi_disable(struct clk *clk) { struct clk_utmi *utmi = to_clk_utmi(clk); pmc_update_bits(utmi->base, AT91_CKGR_UCKR, AT91_PMC_UPLLEN, 0); return 0; } static ulong clk_utmi_get_rate(struct clk *clk) { /* UTMI clk rate is fixed. */ return UTMI_RATE; } static const struct clk_ops utmi_ops = { .enable = clk_utmi_enable, .disable = clk_utmi_disable, .get_rate = clk_utmi_get_rate, }; struct clk *at91_clk_register_utmi(void __iomem *base, struct udevice *dev, const char *name, const char *parent_name) { struct udevice *syscon; struct clk_utmi *utmi; struct clk *clk; int ret; if (!base || !dev || !name || !parent_name) return ERR_PTR(-EINVAL); ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, "regmap-sfr", &syscon); if (ret) return ERR_PTR(ret); utmi = kzalloc(sizeof(*utmi), GFP_KERNEL); if (!utmi) return ERR_PTR(-ENOMEM); utmi->base = base; utmi->regmap_sfr = syscon_get_regmap(syscon); if (!utmi->regmap_sfr) { kfree(utmi); return ERR_PTR(-ENODEV); } clk = &utmi->clk; clk->flags = CLK_GET_RATE_NOCACHE; ret = clk_register(clk, UBOOT_DM_CLK_AT91_UTMI, name, parent_name); if (ret) { kfree(utmi); clk = ERR_PTR(ret); } return clk; } U_BOOT_DRIVER(at91_utmi_clk) = { .name = UBOOT_DM_CLK_AT91_UTMI, .id = UCLASS_CLK, .ops = &utmi_ops, .flags = DM_FLAG_PRE_RELOC, }; static int clk_utmi_sama7g5_enable(struct clk *clk) { struct clk_utmi *utmi = to_clk_utmi(clk); ulong parent_rate = clk_get_parent_rate(clk); unsigned int val; switch (parent_rate) { case 16000000: val = 0; break; case 20000000: val = 2; break; case 24000000: val = 3; break; case 32000000: val = 5; break; default: debug("UTMICK: unsupported main_xtal rate\n"); return -EINVAL; } pmc_write(utmi->base, AT91_PMC_XTALF, val); return 0; } static const struct clk_ops sama7g5_utmi_ops = { .enable = clk_utmi_sama7g5_enable, .get_rate = clk_utmi_get_rate, }; struct clk *at91_clk_sama7g5_register_utmi(void __iomem *base, const char *name, const char *parent_name) { struct clk_utmi *utmi; struct clk *clk; int ret; if (!base || !name || !parent_name) return ERR_PTR(-EINVAL); utmi = kzalloc(sizeof(*utmi), GFP_KERNEL); if (!utmi) return ERR_PTR(-ENOMEM); utmi->base = base; clk = &utmi->clk; ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAMA7G5_UTMI, name, parent_name); if (ret) { kfree(utmi); clk = ERR_PTR(ret); } return clk; } U_BOOT_DRIVER(at91_sama7g5_utmi_clk) = { .name = UBOOT_DM_CLK_AT91_SAMA7G5_UTMI, .id = UCLASS_CLK, .ops = &sama7g5_utmi_ops, .flags = DM_FLAG_PRE_RELOC, };