11d665639SClément Léger // SPDX-License-Identifier: GPL-2.0+ or BSD-3-Clause
21d665639SClément Léger /*
31d665639SClément Léger * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
41d665639SClément Léger * Copyright (C) 2021 Microchip
51d665639SClément Léger */
61d665639SClément Léger
71d665639SClément Léger #include <io.h>
81d665639SClément Léger #include <kernel/delay.h>
91d665639SClément Léger #include <kernel/panic.h>
101d665639SClément Léger #include <mm/core_memprot.h>
111d665639SClément Léger #include <types_ext.h>
121d665639SClément Léger
131d665639SClément Léger #include "at91_clk.h"
141d665639SClément Léger
151d665639SClément Léger #define PERIPHERAL_ID_MIN 2
161d665639SClément Léger #define PERIPHERAL_ID_MASK 31
171d665639SClément Léger #define PERIPHERAL_MASK(id) BIT((id) & PERIPHERAL_ID_MASK)
181d665639SClément Léger
191d665639SClément Léger #define PERIPHERAL_MAX_SHIFT 3
201d665639SClément Léger
211d665639SClément Léger struct clk_sam9x5_peripheral {
221d665639SClément Léger vaddr_t base;
231d665639SClément Léger struct clk_range range;
241d665639SClément Léger uint32_t id;
251d665639SClément Léger uint32_t div;
261d665639SClément Léger const struct clk_pcr_layout *layout;
271d665639SClément Léger bool auto_div;
281d665639SClément Léger };
291d665639SClément Léger
clk_sam9x5_peripheral_autodiv(struct clk * clk)301d665639SClément Léger static void clk_sam9x5_peripheral_autodiv(struct clk *clk)
311d665639SClément Léger {
321d665639SClément Léger struct clk *parent = NULL;
331d665639SClément Léger struct clk_sam9x5_peripheral *periph = clk->priv;
341d665639SClément Léger unsigned long parent_rate = 0;
351d665639SClément Léger int shift = 0;
361d665639SClément Léger
371d665639SClément Léger if (!periph->auto_div)
381d665639SClément Léger return;
391d665639SClément Léger
401d665639SClément Léger if (periph->range.max) {
411d665639SClément Léger parent = clk_get_parent_by_index(clk, 0);
421d665639SClément Léger parent_rate = clk_get_rate(parent);
431d665639SClément Léger if (!parent_rate)
441d665639SClément Léger return;
451d665639SClément Léger
461d665639SClément Léger for (shift = 0; shift < PERIPHERAL_MAX_SHIFT; shift++) {
471d665639SClément Léger if (parent_rate >> shift <= periph->range.max)
481d665639SClément Léger break;
491d665639SClément Léger }
501d665639SClément Léger }
511d665639SClément Léger
521d665639SClément Léger periph->auto_div = false;
531d665639SClément Léger periph->div = shift;
541d665639SClément Léger }
551d665639SClément Léger
clk_sam9x5_peripheral_enable(struct clk * clk)561d665639SClément Léger static TEE_Result clk_sam9x5_peripheral_enable(struct clk *clk)
571d665639SClément Léger {
581d665639SClément Léger struct clk_sam9x5_peripheral *periph = clk->priv;
591d665639SClément Léger
601d665639SClément Léger if (periph->id < PERIPHERAL_ID_MIN)
611d665639SClément Léger return TEE_SUCCESS;
621d665639SClément Léger
631d665639SClément Léger io_write32(periph->base + periph->layout->offset,
641d665639SClément Léger (periph->id & periph->layout->pid_mask));
651d665639SClément Léger io_clrsetbits32(periph->base + periph->layout->offset,
661d665639SClément Léger periph->layout->div_mask | periph->layout->cmd |
671d665639SClément Léger AT91_PMC_PCR_EN,
681d665639SClément Léger field_prep(periph->layout->div_mask, periph->div) |
691d665639SClément Léger periph->layout->cmd |
701d665639SClément Léger AT91_PMC_PCR_EN);
711d665639SClément Léger
721d665639SClément Léger return TEE_SUCCESS;
731d665639SClément Léger }
741d665639SClément Léger
clk_sam9x5_peripheral_disable(struct clk * clk)751d665639SClément Léger static void clk_sam9x5_peripheral_disable(struct clk *clk)
761d665639SClément Léger {
771d665639SClément Léger struct clk_sam9x5_peripheral *periph = clk->priv;
781d665639SClément Léger
791d665639SClément Léger if (periph->id < PERIPHERAL_ID_MIN)
801d665639SClément Léger return;
811d665639SClément Léger
821d665639SClément Léger io_write32(periph->base + periph->layout->offset,
831d665639SClément Léger (periph->id & periph->layout->pid_mask));
841d665639SClément Léger io_clrsetbits32(periph->base + periph->layout->offset,
851d665639SClément Léger AT91_PMC_PCR_EN | periph->layout->cmd,
861d665639SClément Léger periph->layout->cmd);
871d665639SClément Léger }
881d665639SClément Léger
891d665639SClément Léger static unsigned long
clk_sam9x5_peripheral_get_rate(struct clk * clk,unsigned long parent_rate)901d665639SClément Léger clk_sam9x5_peripheral_get_rate(struct clk *clk,
911d665639SClément Léger unsigned long parent_rate)
921d665639SClément Léger {
931d665639SClément Léger struct clk_sam9x5_peripheral *periph = clk->priv;
941d665639SClément Léger uint32_t status = 0;
951d665639SClément Léger
961d665639SClément Léger if (periph->id < PERIPHERAL_ID_MIN)
971d665639SClément Léger return parent_rate;
981d665639SClément Léger
991d665639SClément Léger io_write32(periph->base + periph->layout->offset,
1001d665639SClément Léger periph->id & periph->layout->pid_mask);
1011d665639SClément Léger status = io_read32(periph->base + periph->layout->offset);
1021d665639SClément Léger
1031d665639SClément Léger if (status & AT91_PMC_PCR_EN) {
1041d665639SClément Léger periph->div = field_get(periph->layout->div_mask, status);
1051d665639SClément Léger periph->auto_div = false;
1061d665639SClément Léger } else {
1071d665639SClément Léger clk_sam9x5_peripheral_autodiv(clk);
1081d665639SClément Léger }
1091d665639SClément Léger
1101d665639SClément Léger return parent_rate >> periph->div;
1111d665639SClément Léger }
1121d665639SClément Léger
clk_sam9x5_peripheral_set_rate(struct clk * clk,unsigned long rate,unsigned long parent_rate)1131d665639SClément Léger static TEE_Result clk_sam9x5_peripheral_set_rate(struct clk *clk,
1141d665639SClément Léger unsigned long rate,
1151d665639SClément Léger unsigned long parent_rate)
1161d665639SClément Léger {
1171d665639SClément Léger unsigned int shift = 0;
1181d665639SClément Léger struct clk_sam9x5_peripheral *periph = clk->priv;
1191d665639SClément Léger
1201d665639SClément Léger if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
1211d665639SClément Léger if (parent_rate == rate)
1221d665639SClément Léger return TEE_SUCCESS;
1231d665639SClément Léger else
1241d665639SClément Léger return TEE_ERROR_GENERIC;
1251d665639SClément Léger }
1261d665639SClément Léger
1271d665639SClément Léger if (periph->range.max && rate > periph->range.max)
1281d665639SClément Léger return TEE_ERROR_GENERIC;
1291d665639SClément Léger
1301d665639SClément Léger for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
1311d665639SClément Léger if (parent_rate >> shift == rate) {
1321d665639SClément Léger periph->auto_div = false;
1331d665639SClément Léger periph->div = shift;
1341d665639SClément Léger return TEE_SUCCESS;
1351d665639SClément Léger }
1361d665639SClément Léger }
1371d665639SClément Léger
1381d665639SClément Léger return TEE_ERROR_GENERIC;
1391d665639SClément Léger }
1401d665639SClément Léger
1411d665639SClément Léger static const struct clk_ops sam9x5_peripheral_ops = {
1421d665639SClément Léger .enable = clk_sam9x5_peripheral_enable,
1431d665639SClément Léger .disable = clk_sam9x5_peripheral_disable,
1441d665639SClément Léger .get_rate = clk_sam9x5_peripheral_get_rate,
1451d665639SClément Léger .set_rate = clk_sam9x5_peripheral_set_rate,
1461d665639SClément Léger };
1471d665639SClément Léger
1481d665639SClément Léger struct clk *
at91_clk_register_sam9x5_periph(struct pmc_data * pmc,const struct clk_pcr_layout * layout,const char * name,struct clk * parent,uint32_t id,const struct clk_range * range)1491d665639SClément Léger at91_clk_register_sam9x5_periph(struct pmc_data *pmc,
1501d665639SClément Léger const struct clk_pcr_layout *layout,
1511d665639SClément Léger const char *name, struct clk *parent,
1521d665639SClément Léger uint32_t id, const struct clk_range *range)
1531d665639SClément Léger {
1541d665639SClément Léger struct clk_sam9x5_peripheral *periph = NULL;
1551d665639SClément Léger struct clk *clk = NULL;
1561d665639SClément Léger
1571d665639SClément Léger if (!name || !parent)
1581d665639SClément Léger return NULL;
1591d665639SClément Léger
1601d665639SClément Léger clk = clk_alloc(name, &sam9x5_peripheral_ops, &parent, 1);
1611d665639SClément Léger if (!clk)
1621d665639SClément Léger return NULL;
1631d665639SClément Léger
1641d665639SClément Léger periph = calloc(1, sizeof(*periph));
1651d665639SClément Léger if (!periph) {
1661d665639SClément Léger clk_free(clk);
1671d665639SClément Léger return NULL;
1681d665639SClément Léger }
1691d665639SClément Léger
1701d665639SClément Léger periph->id = id;
1711d665639SClément Léger periph->div = 0;
1721d665639SClément Léger periph->base = pmc->base;
1731d665639SClément Léger if (layout->div_mask)
1741d665639SClément Léger periph->auto_div = true;
1751d665639SClément Léger periph->layout = layout;
1761d665639SClément Léger periph->range = *range;
1771d665639SClément Léger
1781d665639SClément Léger clk->priv = periph;
1791d665639SClément Léger
1801d665639SClément Léger if (clk_register(clk)) {
1811d665639SClément Léger clk_free(clk);
1821d665639SClément Léger free(periph);
1831d665639SClément Léger return 0;
1841d665639SClément Léger }
1851d665639SClément Léger
1861d665639SClément Léger clk_sam9x5_peripheral_autodiv(clk);
187*5e6f824bSClément Léger pmc_register_id(id);
1881d665639SClément Léger
1891d665639SClément Léger return clk;
1901d665639SClément Léger }
191