soc: microchip: sam: add code for sama7g5 clocks
Add code for sama7g5 Generic Clock, Main Clock, Main System Bus Clock, Peripheral Clock, Programmable Clock and PLL Clock. Signed-off-by: Tony Han <tony.han@microchip.com>
This commit is contained in:
parent
a10b87b2b4
commit
21da37b400
13 changed files with 3513 additions and 0 deletions
17
soc/microchip/sam/common/CMakeLists.txt
Normal file
17
soc/microchip/sam/common/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
zephyr_include_directories(.)
|
||||
|
||||
zephyr_sources(clk-generated.c)
|
||||
zephyr_sources(clk-main.c)
|
||||
zephyr_sources(clk-master.c)
|
||||
zephyr_sources(clk-peripheral.c)
|
||||
zephyr_sources(clk-programmable.c)
|
||||
zephyr_sources(clk-sam9x60-pll.c)
|
||||
zephyr_sources(clk-system.c)
|
||||
zephyr_sources(clk-utmi.c)
|
||||
zephyr_sources(pmc.c)
|
||||
zephyr_sources_ifdef(CONFIG_SOC_SERIES_SAMA7G5 sama7g5.c)
|
191
soc/microchip/sam/common/clk-generated.c
Normal file
191
soc/microchip/sam/common/clk-generated.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_generated, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
struct clk_generated {
|
||||
struct device clk;
|
||||
struct device *parents[8];
|
||||
pmc_registers_t *pmc;
|
||||
struct clk_range range;
|
||||
struct k_spinlock *lock;
|
||||
uint32_t mux_table[8];
|
||||
uint32_t id;
|
||||
uint32_t gckdiv;
|
||||
const struct clk_pcr_layout *layout;
|
||||
uint8_t num_parents;
|
||||
uint8_t parent_id;
|
||||
int chg_pid;
|
||||
};
|
||||
|
||||
#define to_clk_generated(ptr) CONTAINER_OF(ptr, struct clk_generated, clk)
|
||||
|
||||
static struct clk_generated clocks_gck[ID_PERIPH_MAX];
|
||||
static uint32_t clocks_gck_idx;
|
||||
|
||||
static int clk_generated_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_generated *gck = to_clk_generated(dev);
|
||||
uint32_t mask = gck->layout->cmd | PMC_PCR_GCLKEN_Msk;
|
||||
uint32_t val = gck->layout->cmd | PMC_PCR_GCLKEN_Msk;
|
||||
k_spinlock_key_t key;
|
||||
|
||||
LOG_DBG("gckdiv = %d, parent id = %d\n", gck->gckdiv, gck->parent_id);
|
||||
|
||||
mask |= PMC_PCR_GCLKDIV_Msk | gck->layout->gckcss_mask;
|
||||
val |= FIELD_PREP(PMC_PCR_GCLKDIV_Msk, gck->gckdiv);
|
||||
val |= FIELD_PREP(gck->layout->gckcss_mask, gck->parent_id);
|
||||
|
||||
key = k_spin_lock(gck->lock);
|
||||
regmap_write((void *)gck->pmc, gck->layout->offset, (gck->id & gck->layout->pid_mask));
|
||||
regmap_update_bits((void *)gck->pmc, gck->layout->offset, mask, val);
|
||||
k_spin_unlock(gck->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_generated_off(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_generated *gck = to_clk_generated(dev);
|
||||
k_spinlock_key_t key = k_spin_lock(gck->lock);
|
||||
|
||||
regmap_write((void *)gck->pmc, gck->layout->offset, (gck->id & gck->layout->pid_mask));
|
||||
regmap_update_bits((void *)gck->pmc, gck->layout->offset,
|
||||
gck->layout->cmd | PMC_PCR_GCLKEN_Msk,
|
||||
gck->layout->cmd);
|
||||
|
||||
k_spin_unlock(gck->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status clk_generated_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_generated *gck = to_clk_generated(dev);
|
||||
k_spinlock_key_t key = k_spin_lock(gck->lock);
|
||||
uint32_t status;
|
||||
|
||||
regmap_write((void *)gck->pmc, gck->layout->offset, (gck->id & gck->layout->pid_mask));
|
||||
regmap_read((void *)gck->pmc, gck->layout->offset, &status);
|
||||
|
||||
k_spin_unlock(gck->lock, key);
|
||||
|
||||
if (!!(status & PMC_PCR_GCLKEN_Msk)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static int clk_generated_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_generated *gck = to_clk_generated(dev);
|
||||
const struct device *parent = NULL;
|
||||
uint32_t status, css, i = 0;
|
||||
k_spinlock_key_t key;
|
||||
int ret;
|
||||
|
||||
if (clk_generated_get_status(dev, sys) == CLOCK_CONTROL_STATUS_OFF) {
|
||||
*rate = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
key = k_spin_lock(gck->lock);
|
||||
regmap_write((void *)gck->pmc, gck->layout->offset, (gck->id & gck->layout->pid_mask));
|
||||
regmap_read((void *)gck->pmc, gck->layout->offset, &status);
|
||||
k_spin_unlock(gck->lock, key);
|
||||
|
||||
while (!(gck->layout->gckcss_mask & (1 << i))) {
|
||||
i++;
|
||||
}
|
||||
css = (status & gck->layout->gckcss_mask) >> i;
|
||||
|
||||
for (i = 0; i < gck->num_parents; i++) {
|
||||
if (css == gck->mux_table[i]) {
|
||||
parent = gck->parents[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parent) {
|
||||
LOG_ERR("find parent clock failed.");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ret = clock_control_get_rate(parent, NULL, rate);
|
||||
if (ret) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
return ret;
|
||||
}
|
||||
*rate /= (gck->gckdiv + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, generated_api) = {
|
||||
.on = clk_generated_on,
|
||||
.off = clk_generated_off,
|
||||
.get_rate = clk_generated_get_rate,
|
||||
.get_status = clk_generated_get_status,
|
||||
};
|
||||
|
||||
int clk_register_generated(pmc_registers_t *const pmc, struct k_spinlock *lock,
|
||||
const struct clk_pcr_layout *layout,
|
||||
const char *name,
|
||||
const struct device **parents, uint32_t *mux_table,
|
||||
uint8_t num_parents, uint8_t id,
|
||||
const struct clk_range *range, int chg_pid, struct device **clk)
|
||||
{
|
||||
struct clk_generated *gck;
|
||||
|
||||
if (!parents) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gck = &clocks_gck[clocks_gck_idx++];
|
||||
if (clocks_gck_idx > ARRAY_SIZE(clocks_gck)) {
|
||||
LOG_ERR("Array for generated clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (num_parents > ARRAY_SIZE(gck->parents)) {
|
||||
LOG_ERR("Array for parent clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (num_parents > ARRAY_SIZE(gck->mux_table)) {
|
||||
LOG_ERR("Array for mux table not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(gck->parents, parents, sizeof(struct device *) * num_parents);
|
||||
|
||||
*clk = &gck->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &generated_api;
|
||||
gck->num_parents = num_parents;
|
||||
gck->id = id;
|
||||
gck->pmc = pmc;
|
||||
gck->lock = lock;
|
||||
gck->range = *range;
|
||||
gck->chg_pid = chg_pid;
|
||||
gck->layout = layout;
|
||||
memcpy(gck->mux_table, mux_table, num_parents * sizeof(mux_table));
|
||||
|
||||
return 0;
|
||||
}
|
356
soc/microchip/sam/common/clk-main.c
Normal file
356
soc/microchip/sam/common/clk-main.c
Normal file
|
@ -0,0 +1,356 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_main, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
#define SLOW_CLOCK_FREQ 32768
|
||||
#define MAINF_DIV 16
|
||||
|
||||
#define clk_main_parent_select(s) (((s) & \
|
||||
(CKGR_MOR_MOSCXTEN_Msk | \
|
||||
AT91_PMC_OSCBYPASS)) ? 1 : 0)
|
||||
|
||||
struct clk_main_osc {
|
||||
struct device clk;
|
||||
|
||||
pmc_registers_t *pmc;
|
||||
uint32_t frequency;
|
||||
};
|
||||
|
||||
#define to_clk_main_osc(ptr) CONTAINER_OF(ptr, struct clk_main_osc, clk)
|
||||
|
||||
struct clk_main_rc_osc {
|
||||
struct device clk;
|
||||
|
||||
pmc_registers_t *pmc;
|
||||
uint32_t frequency;
|
||||
};
|
||||
|
||||
#define to_clk_main_rc_osc(ptr) CONTAINER_OF(ptr, struct clk_main_rc_osc, clk)
|
||||
|
||||
struct clk_main {
|
||||
struct device clk;
|
||||
|
||||
pmc_registers_t *pmc;
|
||||
struct device *parents[2];
|
||||
uint8_t num_parents;
|
||||
uint8_t parent;
|
||||
};
|
||||
|
||||
#define to_clk_main(ptr) CONTAINER_OF(ptr, struct clk_main, clk)
|
||||
|
||||
static struct clk_main_osc main_osc;
|
||||
static struct clk_main_rc_osc main_rc_osc;
|
||||
static struct clk_main clocks_main;
|
||||
|
||||
static int main_osc_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_osc *osc = to_clk_main_osc(dev);
|
||||
pmc_registers_t *pmc = osc->pmc;
|
||||
uint32_t tmp, status;
|
||||
|
||||
tmp = pmc->CKGR_MOR & ~CKGR_MOR_KEY_Msk;
|
||||
|
||||
if (tmp & AT91_PMC_OSCBYPASS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(tmp & CKGR_MOR_MOSCXTEN_Msk)) {
|
||||
tmp |= CKGR_MOR_MOSCXTEN_Msk | CKGR_MOR_KEY_PASSWD;
|
||||
pmc->CKGR_MOR = tmp;
|
||||
}
|
||||
|
||||
do {
|
||||
status = pmc->PMC_SR;
|
||||
} while (!(status & CKGR_MOR_MOSCXTEN_Msk));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int main_osc_off(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_osc *osc = to_clk_main_osc(dev);
|
||||
pmc_registers_t *pmc = osc->pmc;
|
||||
uint32_t tmp;
|
||||
|
||||
tmp = pmc->CKGR_MOR;
|
||||
if (!(tmp & AT91_PMC_OSCBYPASS) && (tmp & CKGR_MOR_MOSCXTEN_Msk)) {
|
||||
tmp &= ~(CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTEN_Msk);
|
||||
pmc->CKGR_MOR = tmp | CKGR_MOR_KEY_PASSWD;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int main_osc_get_rate(const struct device *dev, clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_osc *osc = to_clk_main_osc(dev);
|
||||
|
||||
*rate = osc->frequency;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status main_osc_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_osc *osc = to_clk_main_osc(dev);
|
||||
pmc_registers_t *pmc = osc->pmc;
|
||||
uint32_t tmp, status;
|
||||
|
||||
tmp = pmc->CKGR_MOR;
|
||||
if (tmp & AT91_PMC_OSCBYPASS) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
}
|
||||
|
||||
status = pmc->PMC_SR;
|
||||
if ((status & PMC_SR_MOSCXTS_Msk) && clk_main_parent_select(tmp)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, main_osc_api) = {
|
||||
.on = main_osc_on,
|
||||
.off = main_osc_off,
|
||||
.get_rate = main_osc_get_rate,
|
||||
.get_status = main_osc_get_status,
|
||||
};
|
||||
|
||||
int clk_register_main_osc(pmc_registers_t *const pmc, const char *name, uint32_t bypass,
|
||||
uint32_t frequency, struct device **clk)
|
||||
{
|
||||
struct clk_main_osc *osc;
|
||||
|
||||
if (!name) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
osc = &main_osc;
|
||||
|
||||
*clk = &osc->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &main_osc_api;
|
||||
osc->pmc = pmc;
|
||||
osc->frequency = frequency;
|
||||
|
||||
if (bypass) {
|
||||
reg_update_bits(pmc->CKGR_MOR, CKGR_MOR_KEY_Msk | AT91_PMC_OSCBYPASS,
|
||||
AT91_PMC_OSCBYPASS | CKGR_MOR_KEY_PASSWD);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int main_rc_osc_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_rc_osc *osc = to_clk_main_rc_osc(dev);
|
||||
pmc_registers_t *pmc = osc->pmc;
|
||||
uint32_t mor, status;
|
||||
|
||||
mor = pmc->CKGR_MOR;
|
||||
|
||||
if (!(mor & CKGR_MOR_MOSCRCEN_Msk)) {
|
||||
reg_update_bits(pmc->CKGR_MOR,
|
||||
CKGR_MOR_KEY_Msk | CKGR_MOR_MOSCRCEN_Msk,
|
||||
CKGR_MOR_MOSCRCEN_Msk | CKGR_MOR_KEY_PASSWD);
|
||||
}
|
||||
|
||||
do {
|
||||
status = pmc->PMC_SR;
|
||||
} while (!(status & PMC_SR_MOSCRCS_Msk));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int main_rc_osc_off(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_rc_osc *osc = to_clk_main_rc_osc(dev);
|
||||
pmc_registers_t *pmc = osc->pmc;
|
||||
|
||||
if (pmc->CKGR_MOR & CKGR_MOR_MOSCRCEN_Msk) {
|
||||
reg_update_bits(pmc->CKGR_MOR,
|
||||
CKGR_MOR_KEY_Msk | CKGR_MOR_MOSCRCEN_Msk, CKGR_MOR_KEY_PASSWD);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int main_rc_osc_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_rc_osc *osc = to_clk_main_rc_osc(dev);
|
||||
|
||||
*rate = osc->frequency;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status main_rc_osc_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main_rc_osc *osc = to_clk_main_rc_osc(dev);
|
||||
pmc_registers_t *pmc = osc->pmc;
|
||||
|
||||
if ((pmc->CKGR_MOR & CKGR_MOR_MOSCRCEN_Msk) && (pmc->PMC_SR & PMC_SR_MOSCRCS_Msk)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, main_rc_osc_api) = {
|
||||
.on = main_rc_osc_on,
|
||||
.off = main_rc_osc_off,
|
||||
.get_rate = main_rc_osc_get_rate,
|
||||
.get_status = main_rc_osc_get_status,
|
||||
};
|
||||
|
||||
int clk_register_main_rc_osc(pmc_registers_t *const pmc, const char *name,
|
||||
uint32_t frequency, struct device **clk)
|
||||
{
|
||||
struct clk_main_rc_osc *osc;
|
||||
|
||||
if (!name || !frequency) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
osc = &main_rc_osc;
|
||||
|
||||
*clk = &osc->clk;
|
||||
(*clk)->api = &main_rc_osc_api;
|
||||
(*clk)->name = name;
|
||||
osc->pmc = pmc;
|
||||
osc->frequency = frequency;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_main_probe_frequency(pmc_registers_t *pmc)
|
||||
{
|
||||
uint32_t timeout = 0;
|
||||
|
||||
do {
|
||||
if (pmc->CKGR_MCFR & CKGR_MCFR_MAINFRDY_Msk) {
|
||||
return 0;
|
||||
}
|
||||
k_busy_wait(10);
|
||||
} while (timeout++ < 10);
|
||||
LOG_WRN("Wait Main Clock Frequency Measure Ready timeout.");
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static inline bool clk_main_ready(pmc_registers_t *pmc)
|
||||
{
|
||||
return !!(pmc->PMC_SR & PMC_SR_MOSCSELS_Msk);
|
||||
}
|
||||
|
||||
static int clk_main_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main *clkmain = to_clk_main(dev);
|
||||
pmc_registers_t *pmc = clkmain->pmc;
|
||||
|
||||
while (!clk_main_ready(pmc)) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
return clk_main_probe_frequency(pmc);
|
||||
}
|
||||
|
||||
static int clk_main_get_rate(const struct device *dev, clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main *clkmain = to_clk_main(dev);
|
||||
pmc_registers_t *pmc = clkmain->pmc;
|
||||
uint32_t mcfr;
|
||||
|
||||
if (clkmain->parent == 1) {
|
||||
return clock_control_get_rate(clkmain->parents[1], NULL, rate);
|
||||
}
|
||||
|
||||
mcfr = pmc->CKGR_MCFR;
|
||||
if (!(mcfr & CKGR_MCFR_MAINFRDY_Msk)) {
|
||||
*rate = 0;
|
||||
} else {
|
||||
*rate = ((mcfr & CKGR_MCFR_MAINF_Msk) * SLOW_CLOCK_FREQ) / MAINF_DIV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status clk_main_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_main *clkmain = to_clk_main(dev);
|
||||
|
||||
if (clk_main_ready(clkmain->pmc)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, main_api) = {
|
||||
.on = clk_main_on,
|
||||
.get_rate = clk_main_get_rate,
|
||||
.get_status = clk_main_get_status,
|
||||
};
|
||||
|
||||
int clk_register_main(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device **parents, int num_parents, struct device **clk)
|
||||
{
|
||||
struct clk_main *clkmain;
|
||||
unsigned int status;
|
||||
|
||||
if (!name || !parents || !num_parents) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clkmain = &clocks_main;
|
||||
if (num_parents > ARRAY_SIZE(clkmain->parents)) {
|
||||
LOG_ERR("Array for parent clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(clkmain->parents, parents, sizeof(struct device *) * num_parents);
|
||||
|
||||
*clk = &clkmain->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &main_api;
|
||||
clkmain->num_parents = num_parents;
|
||||
clkmain->pmc = pmc;
|
||||
status = pmc->CKGR_MOR;
|
||||
clkmain->parent = clk_main_parent_select(status);
|
||||
|
||||
return 0;
|
||||
}
|
320
soc/microchip/sam/common/clk-master.c
Normal file
320
soc/microchip/sam/common/clk-master.c
Normal file
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_mck, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
#define MASTER_PRES_MASK 0x7
|
||||
#define MASTER_PRES_MAX MASTER_PRES_MASK
|
||||
#define MASTER_DIV_SHIFT 8
|
||||
#define MASTER_DIV_MASK 0x7
|
||||
|
||||
#define PMC_MCR_CSS_SHIFT 16
|
||||
|
||||
#define MASTER_MAX_ID 4
|
||||
|
||||
#define to_clk_master(ptr) CONTAINER_OF(ptr, struct clk_master, clk)
|
||||
|
||||
struct clk_master {
|
||||
pmc_registers_t *pmc;
|
||||
struct device clk;
|
||||
struct device *parents[6];
|
||||
struct k_spinlock *lock;
|
||||
const struct clk_master_layout *layout;
|
||||
const struct clk_master_characteristics *characteristics;
|
||||
uint32_t mux_table[6];
|
||||
uint32_t mckr;
|
||||
uint8_t id;
|
||||
uint8_t num_parents;
|
||||
uint8_t parent;
|
||||
uint8_t div;
|
||||
};
|
||||
|
||||
static struct clk_master clocks_master[5];
|
||||
static uint32_t clocks_master_idx;
|
||||
|
||||
static inline bool clk_master_ready(struct clk_master *master)
|
||||
{
|
||||
uint32_t bit = master->id ? PMC_SR_MCKXRDY_Msk : PMC_SR_MCKRDY_Msk;
|
||||
uint32_t status;
|
||||
|
||||
status = master->pmc->PMC_SR;
|
||||
|
||||
return !!(status & bit);
|
||||
}
|
||||
|
||||
static int clk_master_div_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_master *master = to_clk_master(dev);
|
||||
k_spinlock_key_t key;
|
||||
|
||||
key = k_spin_lock(master->lock);
|
||||
|
||||
while (!clk_master_ready(master)) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
k_spin_unlock(master->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status clk_master_div_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_master *master = to_clk_master(dev);
|
||||
k_spinlock_key_t key;
|
||||
bool status;
|
||||
|
||||
key = k_spin_lock(master->lock);
|
||||
status = clk_master_ready(master);
|
||||
k_spin_unlock(master->lock, key);
|
||||
|
||||
return status ? CLOCK_CONTROL_STATUS_ON : CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
|
||||
static int clk_master_div_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_master *master = to_clk_master(dev);
|
||||
uint32_t retval = 0;
|
||||
|
||||
retval = clock_control_get_rate(master->parents[master->parent], NULL, rate);
|
||||
if (!retval) {
|
||||
*rate /= master->div;
|
||||
}
|
||||
|
||||
LOG_DBG("%s, rate %d", dev->name, *rate);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, master_div_api) = {
|
||||
.on = clk_master_div_on,
|
||||
.get_rate = clk_master_div_get_rate,
|
||||
.get_status = clk_master_div_get_status,
|
||||
};
|
||||
|
||||
int clk_register_master_div(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device *parent, const struct clk_master_layout *layout,
|
||||
const struct clk_master_characteristics *characteristics,
|
||||
struct k_spinlock *lock, uint32_t safe_div, struct device **clk)
|
||||
{
|
||||
struct clk_master *master;
|
||||
k_spinlock_key_t key;
|
||||
uint32_t mckr;
|
||||
|
||||
if (!name || !lock) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
master = &clocks_master[clocks_master_idx++];
|
||||
if (clocks_master_idx > ARRAY_SIZE(clocks_master)) {
|
||||
LOG_ERR("Array for master clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*clk = &master->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &master_div_api;
|
||||
memcpy(master->parents, &parent, sizeof(struct device *) * 1);
|
||||
master->num_parents = 1;
|
||||
master->parent = 0;
|
||||
master->layout = layout;
|
||||
master->characteristics = characteristics;
|
||||
master->pmc = pmc;
|
||||
master->lock = lock;
|
||||
|
||||
key = k_spin_lock(master->lock);
|
||||
regmap_read((void *)master->pmc, master->layout->offset, &mckr);
|
||||
k_spin_unlock(master->lock, key);
|
||||
|
||||
mckr &= layout->mask;
|
||||
mckr = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
|
||||
master->div = characteristics->divisors[mckr];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_mck_get_rate(const struct device *dev, clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_master *master = to_clk_master(dev);
|
||||
int retval = 0;
|
||||
|
||||
const struct device *parent;
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; ; i++) {
|
||||
if (i >= master->num_parents) {
|
||||
LOG_ERR("clk %s: source %d not found", dev->name, i);
|
||||
return ESRCH;
|
||||
}
|
||||
if (master->mux_table[i] == master->parent) {
|
||||
parent = master->parents[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
retval = clock_control_get_rate(parent, NULL, rate);
|
||||
if (retval) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
} else {
|
||||
if (master->div == MASTER_PRES_MAX) {
|
||||
*rate /= 3;
|
||||
} else {
|
||||
*rate /= 1 << master->div;
|
||||
}
|
||||
}
|
||||
LOG_DBG("clk %s: rate %d", dev->name, *rate);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int clk_mck_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_master *master = to_clk_master(dev);
|
||||
k_spinlock_key_t key;
|
||||
uint32_t val, cparent;
|
||||
uint32_t enable = PMC_MCR_EN_Msk;
|
||||
uint32_t parent = master->parent << PMC_MCR_CSS_SHIFT;
|
||||
uint32_t div = master->div << MASTER_DIV_SHIFT;
|
||||
|
||||
key = k_spin_lock(master->lock);
|
||||
|
||||
master->pmc->PMC_MCR = PMC_MCR_ID(master->id);
|
||||
val = master->pmc->PMC_MCR;
|
||||
reg_update_bits(master->pmc->PMC_MCR,
|
||||
enable | PMC_MCR_CSS_Msk | PMC_MCR_DIV_Msk |
|
||||
PMC_MCR_CMD_Msk | PMC_MCR_ID_Msk,
|
||||
enable | parent | div | PMC_MCR_CMD_Msk |
|
||||
PMC_MCR_ID(master->id));
|
||||
|
||||
cparent = (val & PMC_MCR_CSS_Msk) >> PMC_MCR_CSS_SHIFT;
|
||||
|
||||
/* Wait here only if parent is being changed. */
|
||||
while ((cparent != master->parent) && !clk_master_ready(master)) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
k_spin_unlock(master->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_mck_off(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_master *master = to_clk_master(dev);
|
||||
k_spinlock_key_t key;
|
||||
|
||||
key = k_spin_lock(master->lock);
|
||||
master->pmc->PMC_MCR = master->id;
|
||||
reg_update_bits(master->pmc->PMC_MCR,
|
||||
PMC_MCR_EN_Msk | PMC_MCR_CMD_Msk |
|
||||
PMC_MCR_ID_Msk,
|
||||
PMC_MCR_CMD_Msk |
|
||||
PMC_MCR_ID(master->id));
|
||||
k_spin_unlock(master->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status clk_mck_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_master *master = to_clk_master(dev);
|
||||
k_spinlock_key_t key;
|
||||
uint32_t val;
|
||||
|
||||
key = k_spin_lock(master->lock);
|
||||
|
||||
master->pmc->PMC_MCR = master->id;
|
||||
val = master->pmc->PMC_MCR;
|
||||
|
||||
k_spin_unlock(master->lock, key);
|
||||
|
||||
if (!!(val & PMC_MCR_EN_Msk)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, sama7g5_master_api) = {
|
||||
.on = clk_mck_on,
|
||||
.off = clk_mck_off,
|
||||
.get_rate = clk_mck_get_rate,
|
||||
.get_status = clk_mck_get_status,
|
||||
};
|
||||
|
||||
int clk_register_master(pmc_registers_t *const pmc,
|
||||
const char *name, int num_parents,
|
||||
const struct device **parents,
|
||||
uint32_t *mux_table,
|
||||
struct k_spinlock *lock, uint8_t id,
|
||||
struct device **clk)
|
||||
{
|
||||
struct clk_master *master;
|
||||
k_spinlock_key_t key;
|
||||
unsigned int val;
|
||||
|
||||
if (!num_parents || !parents || !mux_table || !lock || id > MASTER_MAX_ID) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
master = &clocks_master[clocks_master_idx++];
|
||||
if (clocks_master_idx > ARRAY_SIZE(clocks_master)) {
|
||||
LOG_ERR("Array for master clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (num_parents > ARRAY_SIZE(master->parents)) {
|
||||
LOG_ERR("Array for parent clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (num_parents > ARRAY_SIZE(master->mux_table)) {
|
||||
LOG_ERR("Array for mux table not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(master->parents, parents, sizeof(struct device *) * num_parents);
|
||||
|
||||
*clk = &master->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &sama7g5_master_api;
|
||||
|
||||
master->num_parents = num_parents;
|
||||
master->pmc = pmc;
|
||||
master->id = id;
|
||||
master->lock = lock;
|
||||
memcpy(master->mux_table, mux_table, num_parents * sizeof(mux_table));
|
||||
|
||||
key = k_spin_lock(master->lock);
|
||||
pmc->PMC_MCR = master->id;
|
||||
val = pmc->PMC_MCR;
|
||||
master->parent = (val & PMC_MCR_CSS_Msk) >> PMC_MCR_CSS_SHIFT;
|
||||
master->div = (val & PMC_MCR_DIV_Msk) >> MASTER_DIV_SHIFT;
|
||||
k_spin_unlock(master->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
159
soc/microchip/sam/common/clk-peripheral.c
Normal file
159
soc/microchip/sam/common/clk-peripheral.c
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_peripheral, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
#define PERIPHERAL_ID_MIN 2
|
||||
|
||||
struct clk_peripheral {
|
||||
pmc_registers_t *pmc;
|
||||
struct device clk;
|
||||
struct device *parent;
|
||||
struct clk_range range;
|
||||
struct k_spinlock *lock;
|
||||
uint32_t id;
|
||||
const struct clk_pcr_layout *layout;
|
||||
};
|
||||
|
||||
static struct clk_peripheral clocks_periph[72];
|
||||
static uint32_t clocks_periph_idx;
|
||||
|
||||
#define to_clk_peripheral(ptr) CONTAINER_OF(ptr, struct clk_peripheral, clk)
|
||||
|
||||
static void clk_peripheral_set(struct clk_peripheral *periph,
|
||||
uint32_t enable)
|
||||
{
|
||||
k_spinlock_key_t key = k_spin_lock(periph->lock);
|
||||
|
||||
LOG_DBG("id %d, enable %d", periph->id, enable);
|
||||
enable = enable ? PMC_PCR_EN_Msk : 0;
|
||||
regmap_write((void *)periph->pmc, periph->layout->offset,
|
||||
periph->id & periph->layout->pid_mask);
|
||||
regmap_update_bits((void *)periph->pmc, periph->layout->offset,
|
||||
periph->layout->cmd | enable,
|
||||
periph->layout->cmd | enable);
|
||||
|
||||
k_spin_unlock(periph->lock, key);
|
||||
}
|
||||
|
||||
static int clk_peripheral_on(const struct device *clk,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
clk_peripheral_set(to_clk_peripheral(clk), 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_peripheral_off(const struct device *clk,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
clk_peripheral_set(to_clk_peripheral(clk), 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status clk_peripheral_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_peripheral *periph = to_clk_peripheral(dev);
|
||||
k_spinlock_key_t key;
|
||||
uint32_t status;
|
||||
|
||||
if (periph->id < PERIPHERAL_ID_MIN) {
|
||||
return CLOCK_CONTROL_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
key = k_spin_lock(periph->lock);
|
||||
regmap_write((void *)periph->pmc, periph->layout->offset,
|
||||
(periph->id & periph->layout->pid_mask));
|
||||
regmap_read((void *)periph->pmc, periph->layout->offset, &status);
|
||||
k_spin_unlock(periph->lock, key);
|
||||
|
||||
if (!!(status & PMC_PCR_EN_Msk)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static int clk_peripheral_get_rate(const struct device *clk,
|
||||
clock_control_subsys_t sys,
|
||||
uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
uint32_t retval = 0;
|
||||
uint32_t status;
|
||||
|
||||
struct clk_peripheral *periph = to_clk_peripheral(clk);
|
||||
|
||||
regmap_write((void *)periph->pmc, periph->layout->offset,
|
||||
(periph->id & periph->layout->pid_mask));
|
||||
regmap_read((void *)periph->pmc, periph->layout->offset, &status);
|
||||
|
||||
if (status & PMC_PCR_EN_Msk) {
|
||||
retval = clock_control_get_rate(periph->parent, NULL, rate);
|
||||
} else {
|
||||
*rate = 0;
|
||||
}
|
||||
LOG_DBG("id %d, rate %d", periph->id, *rate);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, clk_peripheral_api) = {
|
||||
.on = clk_peripheral_on,
|
||||
.off = clk_peripheral_off,
|
||||
.get_rate = clk_peripheral_get_rate,
|
||||
.get_status = clk_peripheral_get_status,
|
||||
};
|
||||
|
||||
int clk_register_peripheral(pmc_registers_t *const pmc,
|
||||
struct k_spinlock *lock,
|
||||
const struct clk_pcr_layout *layout,
|
||||
const char *name,
|
||||
struct device *parent,
|
||||
uint32_t id,
|
||||
const struct clk_range *range,
|
||||
struct device **clk)
|
||||
{
|
||||
struct clk_peripheral *periph;
|
||||
|
||||
if (!name) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
periph = &clocks_periph[clocks_periph_idx++];
|
||||
if (clocks_periph_idx > ARRAY_SIZE(clocks_periph)) {
|
||||
LOG_ERR("Array for peripheral clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*clk = &periph->clk;
|
||||
periph->clk.name = name;
|
||||
periph->clk.api = &clk_peripheral_api;
|
||||
periph->parent = parent;
|
||||
periph->id = id;
|
||||
periph->pmc = pmc;
|
||||
periph->lock = lock;
|
||||
periph->layout = layout;
|
||||
periph->range = *range;
|
||||
|
||||
return 0;
|
||||
}
|
116
soc/microchip/sam/common/clk-programmable.c
Normal file
116
soc/microchip/sam/common/clk-programmable.c
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_prog, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
#define PROG_ID_MAX 7
|
||||
|
||||
#define PROG_PRES(layout, pckr) ((pckr >> layout->pres_shift) & layout->pres_mask)
|
||||
|
||||
struct clk_programmable {
|
||||
struct device clk;
|
||||
struct device *parents[9];
|
||||
pmc_registers_t *pmc;
|
||||
uint32_t *mux_table;
|
||||
uint8_t id;
|
||||
uint8_t num_parents;
|
||||
uint8_t parent;
|
||||
const struct clk_programmable_layout *layout;
|
||||
};
|
||||
|
||||
#define to_clk_programmable(ptr) CONTAINER_OF(ptr, struct clk_programmable, clk)
|
||||
|
||||
static struct clk_programmable clocks_prog[8];
|
||||
static uint32_t clocks_prog_idx;
|
||||
|
||||
static int clk_programmable_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_programmable *prog = to_clk_programmable(dev);
|
||||
const struct clk_programmable_layout *layout = prog->layout;
|
||||
const struct device *parent = NULL;
|
||||
uint32_t pckr;
|
||||
uint32_t css;
|
||||
uint32_t i;
|
||||
int ret;
|
||||
|
||||
pckr = prog->pmc->PMC_PCK[prog->id];
|
||||
css = pckr & PMC_PCK_CSS_Msk;
|
||||
|
||||
for (i = 0; i < prog->num_parents; i++) {
|
||||
if (css == prog->mux_table[i]) {
|
||||
parent = prog->parents[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parent) {
|
||||
LOG_ERR("find parent clock failed.");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ret = clock_control_get_rate(parent, NULL, rate);
|
||||
if (ret) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (layout->is_pres_direct) {
|
||||
*rate = *rate / (PROG_PRES(layout, pckr) + 1);
|
||||
} else {
|
||||
*rate = *rate >> PROG_PRES(layout, pckr);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, programmable_api) = {
|
||||
.get_rate = clk_programmable_get_rate,
|
||||
};
|
||||
|
||||
int clk_register_programmable(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device **parents,
|
||||
uint8_t num_parents, uint8_t id,
|
||||
const struct clk_programmable_layout *layout,
|
||||
uint32_t *mux_table, struct device **clk)
|
||||
{
|
||||
struct clk_programmable *prog;
|
||||
|
||||
if (id > PROG_ID_MAX || !parents) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
prog = &clocks_prog[clocks_prog_idx++];
|
||||
if (clocks_prog_idx > ARRAY_SIZE(clocks_prog)) {
|
||||
LOG_ERR("Array for programmable clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (num_parents > ARRAY_SIZE(prog->parents)) {
|
||||
LOG_ERR("Array for parent clock not enough, at least %d", num_parents);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(prog->parents, parents, sizeof(struct device *) * num_parents);
|
||||
|
||||
*clk = &prog->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &programmable_api;
|
||||
prog->num_parents = num_parents;
|
||||
prog->id = id;
|
||||
prog->layout = layout;
|
||||
prog->pmc = pmc;
|
||||
prog->mux_table = mux_table;
|
||||
|
||||
return 0;
|
||||
}
|
525
soc/microchip/sam/common/clk-sam9x60-pll.c
Normal file
525
soc/microchip/sam/common/clk-sam9x60-pll.c
Normal file
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_pll, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
#define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0)
|
||||
#define PMC_PLL_CTRL1_MUL_MSK GENMASK(31, 24)
|
||||
#define PMC_PLL_CTRL1_FRACR_MSK GENMASK(21, 0)
|
||||
|
||||
# define do_div(n, base) ({ \
|
||||
uint32_t __base = (base); \
|
||||
uint32_t __rem; \
|
||||
__rem = ((uint64_t)(n)) % __base; \
|
||||
(n) = ((uint64_t)(n)) / __base; \
|
||||
__rem; \
|
||||
})
|
||||
|
||||
#define DIV_ROUND_CLOSEST_ULL(x, divisor)( \
|
||||
{ \
|
||||
__typeof__(divisor) __d = divisor; \
|
||||
unsigned long long _tmp = (x) + (__d) / 2; \
|
||||
do_div(_tmp, __d); \
|
||||
_tmp; \
|
||||
} \
|
||||
)
|
||||
|
||||
/* Calculate "x * n / d" without unnecessary overflow or loss of precision. */
|
||||
#define mult_frac(x, n, d) \
|
||||
({ \
|
||||
__typeof__(x) x_ = (x); \
|
||||
__typeof__(n) n_ = (n); \
|
||||
__typeof__(d) d_ = (d); \
|
||||
\
|
||||
__typeof__(x_) q = x_ / d_; \
|
||||
__typeof__(x_) r = x_ % d_; \
|
||||
q * n_ + r * n_ / d_; \
|
||||
})
|
||||
|
||||
#define PLL_MAX_ID 7
|
||||
|
||||
struct sam9x60_pll_core {
|
||||
pmc_registers_t *pmc;
|
||||
struct k_spinlock *lock;
|
||||
const struct clk_pll_characteristics *characteristics;
|
||||
const struct clk_pll_layout *layout;
|
||||
struct device clk;
|
||||
const struct device *parent;
|
||||
uint8_t id;
|
||||
};
|
||||
|
||||
struct sam9x60_frac {
|
||||
struct sam9x60_pll_core core;
|
||||
uint32_t frac;
|
||||
uint16_t mul;
|
||||
};
|
||||
|
||||
struct sam9x60_div {
|
||||
struct sam9x60_pll_core core;
|
||||
uint8_t div;
|
||||
};
|
||||
|
||||
#define to_sam9x60_pll_core(ptr) CONTAINER_OF(ptr, struct sam9x60_pll_core, clk)
|
||||
#define to_sam9x60_frac(ptr) CONTAINER_OF(ptr, struct sam9x60_frac, core)
|
||||
#define to_sam9x60_div(ptr) CONTAINER_OF(ptr, struct sam9x60_div, core)
|
||||
|
||||
static struct sam9x60_frac clocks_frac[7];
|
||||
static uint32_t clocks_frac_idx;
|
||||
|
||||
static struct sam9x60_div clocks_div[8];
|
||||
static uint32_t clocks_div_idx;
|
||||
|
||||
static inline bool sam9x60_pll_ready(pmc_registers_t *pmc, int id)
|
||||
{
|
||||
unsigned int status;
|
||||
|
||||
status = pmc->PMC_PLL_ISR0;
|
||||
|
||||
return !!(status & BIT(id));
|
||||
}
|
||||
|
||||
static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
|
||||
{
|
||||
struct sam9x60_frac *frac = to_sam9x60_frac(core);
|
||||
|
||||
pmc_registers_t *pmc = core->pmc;
|
||||
uint32_t val, cfrac, cmul;
|
||||
k_spinlock_key_t key;
|
||||
|
||||
key = k_spin_lock(core->lock);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
|
||||
val = pmc->PMC_PLL_CTRL1;
|
||||
cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
|
||||
cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
|
||||
|
||||
if (sam9x60_pll_ready(pmc, core->id) &&
|
||||
(cmul == frac->mul && cfrac == frac->frac)) {
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* Recommended value for PMC_PLL_ACR */
|
||||
if (core->characteristics->upll) {
|
||||
val = AT91_PMC_PLL_ACR_DEFAULT_UPLL;
|
||||
} else {
|
||||
val = AT91_PMC_PLL_ACR_DEFAULT_PLLA;
|
||||
}
|
||||
pmc->PMC_PLL_ACR = val;
|
||||
|
||||
pmc->PMC_PLL_CTRL1 = (frac->mul << core->layout->mul_shift) |
|
||||
(frac->frac << core->layout->frac_shift);
|
||||
|
||||
#ifdef PMC_PLL_ACR_UTMIBG_Msk
|
||||
if (core->characteristics->upll) {
|
||||
/* Enable the UTMI internal bandgap */
|
||||
val |= PMC_PLL_ACR_UTMIBG_Msk;
|
||||
pmc->PMC_PLL_ACR = val;
|
||||
|
||||
k_busy_wait(10);
|
||||
|
||||
/* Enable the UTMI internal regulator */
|
||||
val |= PMC_PLL_ACR_UTMIVR_Msk;
|
||||
pmc->PMC_PLL_ACR = val;
|
||||
|
||||
k_busy_wait(10);
|
||||
}
|
||||
#endif
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | core->id);
|
||||
|
||||
pmc->PMC_PLL_CTRL0 |= PMC_PLL_CTRL0_ENLOCK_Msk | PMC_PLL_CTRL0_ENPLL_Msk;
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | core->id);
|
||||
|
||||
while (!sam9x60_pll_ready(pmc, core->id)) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
unlock:
|
||||
k_spin_unlock(core->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sam9x60_clk_frac_pll_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
|
||||
return sam9x60_frac_pll_set(core);
|
||||
}
|
||||
|
||||
static int sam9x60_clk_frac_pll_off(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
pmc_registers_t *pmc = core->pmc;
|
||||
k_spinlock_key_t key;
|
||||
|
||||
key = k_spin_lock(core->lock);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_CTRL0, PMC_PLL_CTRL0_ENPLL_Msk, 0);
|
||||
|
||||
#ifdef PMC_PLL_ACR_UTMIBG_Msk
|
||||
if (core->characteristics->upll) {
|
||||
reg_update_bits(pmc->PMC_PLL_ACR,
|
||||
PMC_PLL_ACR_UTMIBG_Msk | PMC_PLL_ACR_UTMIVR_Msk, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | core->id);
|
||||
|
||||
k_spin_unlock(core->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status sam9x60_clk_frac_pll_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
|
||||
if (sam9x60_pll_ready(core->pmc, core->id)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static long sam9x60_frac_pll_compute_mul_frac(struct sam9x60_pll_core *core,
|
||||
unsigned long rate,
|
||||
unsigned long parent_rate,
|
||||
bool update)
|
||||
{
|
||||
struct sam9x60_frac *frac = to_sam9x60_frac(core);
|
||||
unsigned long tmprate, remainder;
|
||||
unsigned long nmul = 0;
|
||||
unsigned long nfrac = 0;
|
||||
|
||||
if (rate < core->characteristics->core_output[0].min ||
|
||||
rate > core->characteristics->core_output[0].max) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the multiplier associated with the current
|
||||
* divider that provide the closest rate to the requested one.
|
||||
*/
|
||||
nmul = mult_frac(rate, 1, parent_rate);
|
||||
tmprate = mult_frac(parent_rate, nmul, 1);
|
||||
remainder = rate - tmprate;
|
||||
|
||||
if (remainder) {
|
||||
nfrac = DIV_ROUND_CLOSEST_ULL((uint64_t)remainder * (1 << 22),
|
||||
parent_rate);
|
||||
|
||||
tmprate += DIV_ROUND_CLOSEST_ULL((uint64_t)nfrac * parent_rate,
|
||||
(1 << 22));
|
||||
}
|
||||
|
||||
/* Check if resulted rate is a valid. */
|
||||
if (tmprate < core->characteristics->core_output[0].min ||
|
||||
tmprate > core->characteristics->core_output[0].max) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
frac->mul = nmul - 1;
|
||||
frac->frac = nfrac;
|
||||
}
|
||||
|
||||
return tmprate;
|
||||
}
|
||||
|
||||
static int sam9x60_clk_frac_pll_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
struct sam9x60_frac *frac = to_sam9x60_frac(core);
|
||||
int retval = 0;
|
||||
|
||||
retval = clock_control_get_rate(frac->core.parent, NULL, rate);
|
||||
if (retval) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
*rate = 0;
|
||||
} else {
|
||||
*rate = *rate * (frac->mul + 1) +
|
||||
DIV_ROUND_CLOSEST_ULL((uint64_t)*rate * frac->frac, (1<<22));
|
||||
}
|
||||
LOG_DBG("clk %s: rate %d", dev->name, *rate);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, frac_pll_api) = {
|
||||
.on = sam9x60_clk_frac_pll_on,
|
||||
.off = sam9x60_clk_frac_pll_off,
|
||||
.get_rate = sam9x60_clk_frac_pll_get_rate,
|
||||
.get_status = sam9x60_clk_frac_pll_get_status,
|
||||
};
|
||||
|
||||
int sam9x60_clk_register_frac_pll(pmc_registers_t *const pmc, struct k_spinlock *lock,
|
||||
const char *name,
|
||||
const struct device *parent, uint8_t id,
|
||||
const struct clk_pll_characteristics *characteristics,
|
||||
const struct clk_pll_layout *layout, struct device **clk)
|
||||
{
|
||||
struct sam9x60_frac *frac;
|
||||
uint32_t parent_rate;
|
||||
k_spinlock_key_t key;
|
||||
unsigned int val;
|
||||
int ret = 0;
|
||||
|
||||
if (id > PLL_MAX_ID || !lock || !parent) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
frac = &clocks_frac[clocks_frac_idx++];
|
||||
if (clocks_frac_idx > ARRAY_SIZE(clocks_frac)) {
|
||||
LOG_ERR("Array for PLL frac clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*clk = &frac->core.clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &frac_pll_api;
|
||||
frac->core.parent = parent;
|
||||
frac->core.id = id;
|
||||
frac->core.characteristics = characteristics;
|
||||
frac->core.layout = layout;
|
||||
frac->core.pmc = pmc;
|
||||
frac->core.lock = lock;
|
||||
|
||||
key = k_spin_lock(frac->core.lock);
|
||||
if (sam9x60_pll_ready(pmc, id)) {
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, id);
|
||||
val = pmc->PMC_PLL_CTRL1;
|
||||
frac->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val);
|
||||
frac->frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val);
|
||||
} else {
|
||||
/*
|
||||
* This means the PLL is not setup by bootloaders. In this
|
||||
* case we need to set the minimum rate for it. Otherwise
|
||||
* a clock child of this PLL may be enabled before setting
|
||||
* its rate leading to enabling this PLL with unsupported
|
||||
* rate. This will lead to PLL not being locked at all.
|
||||
*/
|
||||
ret = clock_control_get_rate(parent, NULL, &parent_rate);
|
||||
if (ret) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
goto free;
|
||||
}
|
||||
|
||||
long tmp = sam9x60_frac_pll_compute_mul_frac(&frac->core,
|
||||
characteristics->core_output[0].min,
|
||||
parent_rate, true);
|
||||
if (tmp < 0) {
|
||||
ret = -ENOTSUP;
|
||||
goto free;
|
||||
}
|
||||
}
|
||||
k_spin_unlock(frac->core.lock, key);
|
||||
|
||||
return ret;
|
||||
|
||||
free:
|
||||
k_spin_unlock(frac->core.lock, key);
|
||||
k_free(frac);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* This function should be called with spinlock acquired. */
|
||||
static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, uint32_t div, bool enable)
|
||||
{
|
||||
pmc_registers_t *pmc = core->pmc;
|
||||
uint32_t ena_msk = enable ? core->layout->endiv_mask : 0;
|
||||
uint32_t ena_val = enable ? (1 << core->layout->endiv_shift) : 0;
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_CTRL0,
|
||||
core->layout->div_mask | ena_msk,
|
||||
(div << core->layout->div_shift) | ena_val);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | core->id);
|
||||
|
||||
while (!sam9x60_pll_ready(pmc, core->id)) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
}
|
||||
|
||||
static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
|
||||
{
|
||||
struct sam9x60_div *div = to_sam9x60_div(core);
|
||||
pmc_registers_t *pmc = core->pmc;
|
||||
k_spinlock_key_t key;
|
||||
uint32_t val, cdiv;
|
||||
|
||||
key = k_spin_lock(core->lock);
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
|
||||
val = pmc->PMC_PLL_CTRL0;
|
||||
cdiv = (val & core->layout->div_mask) >> core->layout->div_shift;
|
||||
|
||||
/* Stop if enabled an nothing changed. */
|
||||
if (!!(val & core->layout->endiv_mask) && cdiv == div->div) {
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
sam9x60_div_pll_set_div(core, div->div, 1);
|
||||
|
||||
unlock:
|
||||
k_spin_unlock(core->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sam9x60_clk_div_pll_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
|
||||
return sam9x60_div_pll_set(core);
|
||||
}
|
||||
|
||||
static int sam9x60_clk_div_pll_off(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
pmc_registers_t *pmc = core->pmc;
|
||||
k_spinlock_key_t key;
|
||||
|
||||
key = k_spin_lock(core->lock);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_CTRL0, core->layout->endiv_mask, 0);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | PMC_PLL_UPDT_ID_Msk,
|
||||
PMC_PLL_UPDT_UPDATE_Msk | core->id);
|
||||
|
||||
k_spin_unlock(core->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum clock_control_status sam9x60_clk_div_pll_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
pmc_registers_t *pmc = core->pmc;
|
||||
k_spinlock_key_t key;
|
||||
uint32_t val;
|
||||
|
||||
key = k_spin_lock(core->lock);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, core->id);
|
||||
val = pmc->PMC_PLL_CTRL0;
|
||||
|
||||
k_spin_unlock(core->lock, key);
|
||||
|
||||
if (!!(val & core->layout->endiv_mask)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static int sam9x60_clk_div_pll_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct sam9x60_pll_core *core = to_sam9x60_pll_core(dev);
|
||||
struct sam9x60_div *div = to_sam9x60_div(core);
|
||||
int retval = 0;
|
||||
|
||||
retval = clock_control_get_rate(div->core.parent, NULL, rate);
|
||||
if (retval) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
*rate = 0;
|
||||
} else {
|
||||
*rate = DIV_ROUND_CLOSEST_ULL(*rate, div->div + 1);
|
||||
}
|
||||
LOG_DBG("clk %s: rate %d", dev->name, *rate);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, div_pll_api) = {
|
||||
.on = sam9x60_clk_div_pll_on,
|
||||
.off = sam9x60_clk_div_pll_off,
|
||||
.get_rate = sam9x60_clk_div_pll_get_rate,
|
||||
.get_status = sam9x60_clk_div_pll_get_status,
|
||||
};
|
||||
|
||||
int sam9x60_clk_register_div_pll(pmc_registers_t *const pmc, struct k_spinlock *lock,
|
||||
const char *name,
|
||||
const struct device *parent, uint8_t id,
|
||||
const struct clk_pll_characteristics *characteristics,
|
||||
const struct clk_pll_layout *layout,
|
||||
struct device **clk)
|
||||
{
|
||||
struct sam9x60_div *div;
|
||||
k_spinlock_key_t key;
|
||||
uint32_t val;
|
||||
|
||||
/* We only support one changeable PLL. */
|
||||
if (id > PLL_MAX_ID || !lock || !parent) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
div = &clocks_div[clocks_div_idx++];
|
||||
if (clocks_div_idx > ARRAY_SIZE(clocks_div)) {
|
||||
LOG_ERR("Array for PLL div clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*clk = &div->core.clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &div_pll_api;
|
||||
div->core.parent = parent;
|
||||
div->core.id = id;
|
||||
div->core.characteristics = characteristics;
|
||||
div->core.layout = layout;
|
||||
div->core.pmc = pmc;
|
||||
div->core.lock = lock;
|
||||
|
||||
key = k_spin_lock(div->core.lock);
|
||||
|
||||
reg_update_bits(pmc->PMC_PLL_UPDT, PMC_PLL_UPDT_ID_Msk, id);
|
||||
val = pmc->PMC_PLL_CTRL0;
|
||||
div->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val);
|
||||
|
||||
k_spin_unlock(div->core.lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
141
soc/microchip/sam/common/clk-system.c
Normal file
141
soc/microchip/sam/common/clk-system.c
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_system, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
#define SYSTEM_MAX_ID 31
|
||||
|
||||
#define SYSTEM_MAX_NAME_SZ 32
|
||||
|
||||
#define to_clk_system(ptr) CONTAINER_OF(ptr, struct clk_system, clk)
|
||||
|
||||
struct clk_system {
|
||||
struct device clk;
|
||||
const struct device *parent;
|
||||
pmc_registers_t *pmc;
|
||||
uint8_t id;
|
||||
};
|
||||
|
||||
static struct clk_system clocks_sys[8];
|
||||
static uint32_t clocks_sys_idx;
|
||||
|
||||
static inline int is_pck(int id)
|
||||
{
|
||||
return (id >= 8) && (id <= 15);
|
||||
}
|
||||
|
||||
static inline bool clk_system_ready(pmc_registers_t *pmc, int id)
|
||||
{
|
||||
uint32_t status = pmc->PMC_SR;
|
||||
|
||||
return !!(status & (1 << id));
|
||||
}
|
||||
|
||||
static int clk_system_on(const struct device *dev, clock_control_subsys_t subsys)
|
||||
{
|
||||
ARG_UNUSED(subsys);
|
||||
|
||||
struct clk_system *sys = to_clk_system(dev);
|
||||
|
||||
sys->pmc->PMC_SCER = 1 << sys->id;
|
||||
|
||||
if (!is_pck(sys->id)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (!clk_system_ready(sys->pmc, sys->id)) {
|
||||
k_busy_wait(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_system_off(const struct device *dev, clock_control_subsys_t subsys)
|
||||
{
|
||||
ARG_UNUSED(subsys);
|
||||
|
||||
struct clk_system *sys = to_clk_system(dev);
|
||||
|
||||
sys->pmc->PMC_SCDR = 1 << sys->id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_system_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t subsys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(subsys);
|
||||
|
||||
struct clk_system *sys = to_clk_system(dev);
|
||||
|
||||
return clock_control_get_rate(sys->parent, NULL, rate);
|
||||
}
|
||||
|
||||
static enum clock_control_status clk_system_get_status(const struct device *dev,
|
||||
clock_control_subsys_t subsys)
|
||||
{
|
||||
ARG_UNUSED(subsys);
|
||||
|
||||
struct clk_system *sys = to_clk_system(dev);
|
||||
uint32_t status;
|
||||
|
||||
status = sys->pmc->PMC_SCSR;
|
||||
|
||||
if (!(status & (1 << sys->id))) {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
|
||||
if (!is_pck(sys->id)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
}
|
||||
|
||||
status = sys->pmc->PMC_SR;
|
||||
|
||||
if (!!(status & (1 << sys->id))) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
} else {
|
||||
return CLOCK_CONTROL_STATUS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, system_api) = {
|
||||
.on = clk_system_on,
|
||||
.off = clk_system_off,
|
||||
.get_rate = clk_system_get_rate,
|
||||
.get_status = clk_system_get_status,
|
||||
};
|
||||
|
||||
int clk_register_system(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device *parent,
|
||||
uint8_t id, struct device **clk)
|
||||
{
|
||||
struct clk_system *sys;
|
||||
|
||||
if (!parent || id > SYSTEM_MAX_ID) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sys = &clocks_sys[clocks_sys_idx++];
|
||||
if (clocks_sys_idx > ARRAY_SIZE(clocks_sys)) {
|
||||
LOG_ERR("Array for system clock not enough");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
*clk = &sys->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = &system_api;
|
||||
sys->parent = parent;
|
||||
sys->id = id;
|
||||
sys->pmc = pmc;
|
||||
|
||||
return 0;
|
||||
}
|
154
soc/microchip/sam/common/clk-utmi.c
Normal file
154
soc/microchip/sam/common/clk-utmi.c
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(clk_utmi, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
/*
|
||||
* The purpose of this clock is to generate a 480 MHz signal. A different
|
||||
* rate can't be configured.
|
||||
*/
|
||||
#define UTMI_RATE MHZ(48)
|
||||
|
||||
struct clk_utmi {
|
||||
struct device clk;
|
||||
const struct device *parent;
|
||||
pmc_registers_t *pmc;
|
||||
};
|
||||
|
||||
#define to_clk_utmi(ptr) CONTAINER_OF(ptr, struct clk_utmi, clk)
|
||||
|
||||
static struct clk_utmi clock_utmi;
|
||||
|
||||
static int clk_utmi_sama7g5_get_rate(const struct device *dev,
|
||||
clock_control_subsys_t sys, uint32_t *rate)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
/* UTMI clk rate is fixed. */
|
||||
*rate = UTMI_RATE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at91_clk_register_utmi_internal(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device *parent, const void *api,
|
||||
struct device **clk)
|
||||
{
|
||||
struct clk_utmi *utmi = &clock_utmi;
|
||||
|
||||
if (!parent) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*clk = &utmi->clk;
|
||||
(*clk)->name = name;
|
||||
(*clk)->api = api;
|
||||
utmi->parent = parent;
|
||||
utmi->pmc = pmc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clk_utmi_sama7g5_on(const struct device *dev, clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_utmi *utmi = to_clk_utmi(dev);
|
||||
uint32_t parent_rate;
|
||||
uint32_t val;
|
||||
int ret;
|
||||
|
||||
ret = clock_control_get_rate(utmi->parent, NULL, &parent_rate);
|
||||
if (ret) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (parent_rate) {
|
||||
case MHZ(16):
|
||||
val = 0;
|
||||
break;
|
||||
case MHZ(20):
|
||||
val = 2;
|
||||
break;
|
||||
case MHZ(24):
|
||||
val = 3;
|
||||
break;
|
||||
case MHZ(32):
|
||||
val = 5;
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("UTMICK: unsupported main_xtal rate\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
utmi->pmc->PMC_XTALF = val;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static enum clock_control_status clk_utmi_sama7g5_get_status(const struct device *dev,
|
||||
clock_control_subsys_t sys)
|
||||
{
|
||||
ARG_UNUSED(sys);
|
||||
|
||||
struct clk_utmi *utmi = to_clk_utmi(dev);
|
||||
uint32_t parent_rate;
|
||||
int ret;
|
||||
|
||||
ret = clock_control_get_rate(utmi->parent, NULL, &parent_rate);
|
||||
if (ret) {
|
||||
LOG_ERR("get parent clock rate failed.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (utmi->pmc->PMC_XTALF & 0x7) {
|
||||
case 0:
|
||||
if (parent_rate == MHZ(16)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (parent_rate == MHZ(20)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (parent_rate == MHZ(24)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (parent_rate == MHZ(32)) {
|
||||
return CLOCK_CONTROL_STATUS_ON;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CLOCK_CONTROL_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
static DEVICE_API(clock_control, sama7g5_utmi_api) = {
|
||||
.on = clk_utmi_sama7g5_on,
|
||||
.get_rate = clk_utmi_sama7g5_get_rate,
|
||||
.get_status = clk_utmi_sama7g5_get_status,
|
||||
};
|
||||
|
||||
int clk_register_utmi(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device *parent, struct device **clk)
|
||||
{
|
||||
return at91_clk_register_utmi_internal(pmc, name, parent, &sama7g5_utmi_api, clk);
|
||||
}
|
32
soc/microchip/sam/common/pinctrl_soc.h
Normal file
32
soc/microchip/sam/common/pinctrl_soc.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_SOC_MICROCHIP_SAM_COMMON_PINCTRL_SOC_H_
|
||||
#define ZEPHYR_SOC_MICROCHIP_SAM_COMMON_PINCTRL_SOC_H_
|
||||
|
||||
typedef struct pinctrl_soc_pin_t {
|
||||
uint32_t pin_mux;
|
||||
uint32_t bias_disable: 1;
|
||||
uint32_t bias_pull_down: 1;
|
||||
uint32_t bias_pull_up: 1;
|
||||
uint32_t drive_open_drain: 1;
|
||||
} pinctrl_soc_pin_t;
|
||||
|
||||
#define Z_PINCTRL_STATE_PIN_INIT(node_id, prop, idx) { \
|
||||
.pin_mux = DT_PROP_BY_IDX(node_id, prop, idx), \
|
||||
.bias_disable = DT_PROP(node_id, bias_disable), \
|
||||
.bias_pull_down = DT_PROP(node_id, bias_pull_down), \
|
||||
.bias_pull_up = DT_PROP(node_id, bias_pull_up), \
|
||||
.drive_open_drain = DT_PROP(node_id, drive_open_drain), \
|
||||
},
|
||||
|
||||
#define Z_PINCTRL_STATE_PINS_INIT(node_id, prop) { \
|
||||
DT_FOREACH_CHILD_VARGS(DT_PHANDLE(node_id, prop), \
|
||||
DT_FOREACH_PROP_ELEM, pinmux, \
|
||||
Z_PINCTRL_STATE_PIN_INIT) \
|
||||
}
|
||||
|
||||
#endif /* ZEPHYR_SOC_MICROCHIP_SAM_COMMON_PINCTRL_SOC_H_ */
|
77
soc/microchip/sam/common/pmc.c
Normal file
77
soc/microchip/sam/common/pmc.c
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include <pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(pmc_data, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
|
||||
|
||||
static struct pmc_data pmc_clocks;
|
||||
|
||||
struct device *sam_pmc_get_clock(const struct sam_clk_cfg *cfg,
|
||||
struct pmc_data *ptr)
|
||||
{
|
||||
uint32_t idx = cfg->clock_id;
|
||||
uint32_t type = cfg->clock_type;
|
||||
|
||||
switch (type) {
|
||||
case PMC_TYPE_CORE:
|
||||
if (idx < ptr->ncore) {
|
||||
return ptr->chws[idx];
|
||||
}
|
||||
break;
|
||||
case PMC_TYPE_SYSTEM:
|
||||
if (idx < ptr->nsystem) {
|
||||
return ptr->shws[idx];
|
||||
}
|
||||
break;
|
||||
case PMC_TYPE_PERIPHERAL:
|
||||
if (idx < ptr->nperiph) {
|
||||
return ptr->phws[idx];
|
||||
}
|
||||
break;
|
||||
case PMC_TYPE_GCK:
|
||||
if (idx < ptr->ngck) {
|
||||
return ptr->ghws[idx];
|
||||
}
|
||||
break;
|
||||
case PMC_TYPE_PROGRAMMABLE:
|
||||
if (idx < ptr->npck) {
|
||||
return ptr->pchws[idx];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
|
||||
unsigned int nperiph, unsigned int ngck,
|
||||
unsigned int npck, struct device **table)
|
||||
{
|
||||
struct pmc_data *ptr = &pmc_clocks;
|
||||
|
||||
ptr->ncore = ncore;
|
||||
ptr->chws = table;
|
||||
|
||||
ptr->nsystem = nsystem;
|
||||
ptr->shws = ptr->chws + ncore;
|
||||
|
||||
ptr->nperiph = nperiph;
|
||||
ptr->phws = ptr->shws + nsystem;
|
||||
|
||||
ptr->ngck = ngck;
|
||||
ptr->ghws = ptr->phws + nperiph;
|
||||
|
||||
ptr->npck = npck;
|
||||
ptr->pchws = ptr->ghws + ngck;
|
||||
|
||||
return ptr;
|
||||
}
|
184
soc/microchip/sam/common/pmc.h
Normal file
184
soc/microchip/sam/common/pmc.h
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __SAM_COMMON_PMC_H_
|
||||
#define __SAM_COMMON_PMC_H_
|
||||
|
||||
#include <zephyr/drivers/clock_control/mchp_sam_pmc.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/spinlock.h>
|
||||
|
||||
#define AT91_PMC_PLL_ACR_DEFAULT_UPLL 0x12020010 /* Default PLL ACR value for UPLL */
|
||||
#define AT91_PMC_PLL_ACR_DEFAULT_PLLA 0x00020010 /* Default PLL ACR value for PLLA */
|
||||
|
||||
#define AT91_PMC_OSCBYPASS (1 << 1) /* Oscillator Bypass */
|
||||
|
||||
struct pmc_data {
|
||||
unsigned int ncore;
|
||||
struct device **chws;
|
||||
unsigned int nsystem;
|
||||
struct device **shws;
|
||||
unsigned int nperiph;
|
||||
struct device **phws;
|
||||
unsigned int ngck;
|
||||
struct device **ghws;
|
||||
unsigned int npck;
|
||||
struct device **pchws;
|
||||
};
|
||||
|
||||
struct clk_range {
|
||||
unsigned long min;
|
||||
unsigned long max;
|
||||
};
|
||||
|
||||
struct clk_master_layout {
|
||||
uint32_t offset;
|
||||
uint32_t mask;
|
||||
uint8_t pres_shift;
|
||||
};
|
||||
|
||||
struct clk_master_characteristics {
|
||||
struct clk_range output;
|
||||
uint32_t divisors[5];
|
||||
uint8_t have_div3_pres;
|
||||
};
|
||||
|
||||
struct clk_pll_layout {
|
||||
uint32_t pllr_mask;
|
||||
uint32_t mul_mask;
|
||||
uint32_t frac_mask;
|
||||
uint32_t div_mask;
|
||||
uint32_t endiv_mask;
|
||||
uint32_t mul_shift;
|
||||
uint8_t frac_shift;
|
||||
uint8_t div_shift;
|
||||
uint8_t endiv_shift;
|
||||
uint8_t div2;
|
||||
};
|
||||
|
||||
struct clk_pll_characteristics {
|
||||
struct clk_range input;
|
||||
int num_output;
|
||||
const struct clk_range *output;
|
||||
const struct clk_range *core_output;
|
||||
uint16_t *icpll;
|
||||
uint8_t *out;
|
||||
uint8_t upll : 1;
|
||||
};
|
||||
|
||||
struct clk_programmable_layout {
|
||||
uint8_t pres_mask;
|
||||
uint8_t pres_shift;
|
||||
uint8_t css_mask;
|
||||
uint8_t have_slck_mck;
|
||||
uint8_t is_pres_direct;
|
||||
};
|
||||
|
||||
struct clk_pcr_layout {
|
||||
uint32_t offset;
|
||||
uint32_t cmd;
|
||||
uint32_t gckcss_mask;
|
||||
uint32_t pid_mask;
|
||||
};
|
||||
|
||||
|
||||
static inline void regmap_read(const volatile uint32_t *map,
|
||||
uint32_t reg, uint32_t *val)
|
||||
{
|
||||
*val = *(volatile uint32_t *)((uint32_t)map + reg);
|
||||
}
|
||||
|
||||
static inline void regmap_write(const volatile uint32_t *map,
|
||||
uint32_t reg, uint32_t val)
|
||||
{
|
||||
*(volatile uint32_t *)((uint32_t)map + reg) = val;
|
||||
}
|
||||
|
||||
static inline void regmap_update_bits(const volatile uint32_t *map,
|
||||
uint32_t reg,
|
||||
uint32_t mask, uint32_t val)
|
||||
{
|
||||
uint32_t r = *(volatile uint32_t *)((uint32_t)map + reg);
|
||||
*(volatile uint32_t *)((uint32_t)map + reg) = (r & (~mask)) | val;
|
||||
}
|
||||
|
||||
#define reg_update_bits(reg, mask, val) reg = (reg & (~mask)) | (val)
|
||||
|
||||
struct device *sam_pmc_get_clock(const struct sam_clk_cfg *cfg,
|
||||
struct pmc_data *pmc_data);
|
||||
|
||||
struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
|
||||
unsigned int nperiph, unsigned int ngck,
|
||||
unsigned int npck, struct device **table);
|
||||
|
||||
|
||||
void sam_pmc_setup(const struct device *dev);
|
||||
|
||||
int clk_register_generated(pmc_registers_t *const pmc, struct k_spinlock *lock,
|
||||
const struct clk_pcr_layout *layout,
|
||||
const char *name,
|
||||
const struct device **parents, uint32_t *mux_table,
|
||||
uint8_t num_parents, uint8_t id,
|
||||
const struct clk_range *range, int chg_pid, struct device **clk);
|
||||
|
||||
int clk_register_main_rc_osc(pmc_registers_t *const pmc, const char *name,
|
||||
uint32_t frequency, struct device **clk);
|
||||
|
||||
int clk_register_main_osc(pmc_registers_t *const pmc, const char *name, uint32_t bypass,
|
||||
uint32_t frequency, struct device **clk);
|
||||
|
||||
int clk_register_main(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device **parents, int num_parents, struct device **clk);
|
||||
|
||||
int clk_register_master_div(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device *parent, const struct clk_master_layout *layout,
|
||||
const struct clk_master_characteristics *characteristics,
|
||||
struct k_spinlock *lock, uint32_t safe_div, struct device **clk);
|
||||
|
||||
int clk_register_master(pmc_registers_t *const pmc,
|
||||
const char *name, int num_parents,
|
||||
const struct device **parents,
|
||||
uint32_t *mux_table,
|
||||
struct k_spinlock *lock, uint8_t id,
|
||||
struct device **clk);
|
||||
|
||||
int clk_register_peripheral(pmc_registers_t *const pmc,
|
||||
struct k_spinlock *lock,
|
||||
const struct clk_pcr_layout *layout,
|
||||
const char *name,
|
||||
struct device *parent,
|
||||
uint32_t id,
|
||||
const struct clk_range *range,
|
||||
struct device **clk);
|
||||
|
||||
int sam9x60_clk_register_div_pll(pmc_registers_t *const pmc, struct k_spinlock *lock,
|
||||
const char *name,
|
||||
const struct device *parent, uint8_t id,
|
||||
const struct clk_pll_characteristics *characteristics,
|
||||
const struct clk_pll_layout *layout,
|
||||
struct device **clk);
|
||||
|
||||
int sam9x60_clk_register_frac_pll(pmc_registers_t *const pmc, struct k_spinlock *lock,
|
||||
const char *name,
|
||||
const struct device *parent, uint8_t id,
|
||||
const struct clk_pll_characteristics *characteristics,
|
||||
const struct clk_pll_layout *layout, struct device **clk);
|
||||
|
||||
int clk_register_programmable(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device **parents,
|
||||
uint8_t num_parents, uint8_t id,
|
||||
const struct clk_programmable_layout *layout,
|
||||
uint32_t *mux_table, struct device **clk);
|
||||
|
||||
int clk_register_system(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device *parent,
|
||||
uint8_t id, struct device **clk);
|
||||
|
||||
int clk_register_utmi(pmc_registers_t *const pmc, const char *name,
|
||||
const struct device *parent, struct device **clk);
|
||||
|
||||
#endif /* __SAM_COMMON_PMC_H_ */
|
1241
soc/microchip/sam/common/sama7g5.c
Normal file
1241
soc/microchip/sam/common/sama7g5.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue