// SPDX-License-Identifier: GPL-2.0+ /* * Slow clock support for AT91 architectures. * * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries * * Author: Claudiu Beznea */ #include #include #include #include #include #include "pmc.h" #define UBOOT_DM_CLK_AT91_SAM9X60_TD_SLCK "at91-sam9x60-td-slck" #define UBOOT_DM_CLK_AT91_SCKC "at91-sckc" #define AT91_OSC_SEL BIT(24) #define AT91_OSC_SEL_SHIFT (24) struct sam9x60_sckc { void __iomem *reg; const char **parent_names; unsigned int num_parents; struct clk clk; }; #define to_sam9x60_sckc(c) container_of(c, struct sam9x60_sckc, clk) static int sam9x60_sckc_of_xlate(struct clk *clk, struct ofnode_phandle_args *args) { if (args->args_count != 1) { debug("AT91: SCKC: Invalid args_count: %d\n", args->args_count); return -EINVAL; } clk->id = AT91_TO_CLK_ID(PMC_TYPE_SLOW, args->args[0]); return 0; } static const struct clk_ops sam9x60_sckc_ops = { .of_xlate = sam9x60_sckc_of_xlate, .get_rate = clk_generic_get_rate, }; static int sam9x60_td_slck_set_parent(struct clk *clk, struct clk *parent) { struct sam9x60_sckc *sckc = to_sam9x60_sckc(clk); u32 i; for (i = 0; i < sckc->num_parents; i++) { if (!strcmp(parent->dev->name, sckc->parent_names[i])) break; } if (i == sckc->num_parents) return -EINVAL; pmc_update_bits(sckc->reg, 0, AT91_OSC_SEL, (i << AT91_OSC_SEL_SHIFT)); return 0; } static const struct clk_ops sam9x60_td_slck_ops = { .get_rate = clk_generic_get_rate, .set_parent = sam9x60_td_slck_set_parent, }; static struct clk *at91_sam9x60_clk_register_td_slck(struct sam9x60_sckc *sckc, const char *name, const char * const *parent_names, int num_parents) { struct clk *clk; int ret = -ENOMEM; u32 val, i; if (!sckc || !name || !parent_names || num_parents != 2) return ERR_PTR(-EINVAL); sckc->parent_names = kzalloc(sizeof(*sckc->parent_names) * num_parents, GFP_KERNEL); if (!sckc->parent_names) return ERR_PTR(ret); for (i = 0; i < num_parents; i++) { sckc->parent_names[i] = kmemdup(parent_names[i], strlen(parent_names[i]) + 1, GFP_KERNEL); if (!sckc->parent_names[i]) goto free; } sckc->num_parents = num_parents; pmc_read(sckc->reg, 0, &val); val = (val & AT91_OSC_SEL) >> AT91_OSC_SEL_SHIFT; clk = &sckc->clk; ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X60_TD_SLCK, name, parent_names[val]); if (ret) goto free; return clk; free: for (; i >= 0; i--) kfree(sckc->parent_names[i]); kfree(sckc->parent_names); return ERR_PTR(ret); } U_BOOT_DRIVER(at91_sam9x60_td_slck) = { .name = UBOOT_DM_CLK_AT91_SAM9X60_TD_SLCK, .id = UCLASS_CLK, .ops = &sam9x60_td_slck_ops, .flags = DM_FLAG_PRE_RELOC, }; static int at91_sam9x60_sckc_probe(struct udevice *dev) { struct sam9x60_sckc *sckc = dev_get_priv(dev); void __iomem *base = (void *)devfdt_get_addr(dev); const char *slow_rc_osc, *slow_osc; const char *parents[2]; struct clk *clk, c; int ret; ret = clk_get_by_index(dev, 0, &c); if (ret) return ret; slow_rc_osc = clk_hw_get_name(&c); ret = clk_get_by_index(dev, 1, &c); if (ret) return ret; slow_osc = clk_hw_get_name(&c); clk = clk_register_fixed_factor(NULL, "md_slck", slow_rc_osc, 0, 1, 1); if (IS_ERR(clk)) return PTR_ERR(clk); clk_dm(AT91_TO_CLK_ID(PMC_TYPE_SLOW, 0), clk); parents[0] = slow_rc_osc; parents[1] = slow_osc; sckc[1].reg = base; clk = at91_sam9x60_clk_register_td_slck(&sckc[1], "td_slck", parents, 2); if (IS_ERR(clk)) return PTR_ERR(clk); clk_dm(AT91_TO_CLK_ID(PMC_TYPE_SLOW, 1), clk); return 0; } static const struct udevice_id sam9x60_sckc_ids[] = { { .compatible = "microchip,sam9x60-sckc" }, { /* Sentinel. */ }, }; U_BOOT_DRIVER(at91_sckc) = { .name = UBOOT_DM_CLK_AT91_SCKC, .id = UCLASS_CLK, .of_match = sam9x60_sckc_ids, .priv_auto_alloc_size = sizeof(struct sam9x60_sckc) * 2, .ops = &sam9x60_sckc_ops, .probe = at91_sam9x60_sckc_probe, .flags = DM_FLAG_PRE_RELOC, };