xref: /optee_os/core/drivers/clk/sam/at91_peripheral.c (revision 5e6f824b0640ede149e5a4353b0ab47a7297fd18)
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