diff --git a/soc/microchip/sam/common/CMakeLists.txt b/soc/microchip/sam/common/CMakeLists.txt new file mode 100644 index 00000000000..bbb4727fdf3 --- /dev/null +++ b/soc/microchip/sam/common/CMakeLists.txt @@ -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) diff --git a/soc/microchip/sam/common/clk-generated.c b/soc/microchip/sam/common/clk-generated.c new file mode 100644 index 00000000000..c28e72e0d0e --- /dev/null +++ b/soc/microchip/sam/common/clk-generated.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +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; +} diff --git a/soc/microchip/sam/common/clk-main.c b/soc/microchip/sam/common/clk-main.c new file mode 100644 index 00000000000..5527ddb3318 --- /dev/null +++ b/soc/microchip/sam/common/clk-main.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +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; +} diff --git a/soc/microchip/sam/common/clk-master.c b/soc/microchip/sam/common/clk-master.c new file mode 100644 index 00000000000..e8de582d95f --- /dev/null +++ b/soc/microchip/sam/common/clk-master.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +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; +} diff --git a/soc/microchip/sam/common/clk-peripheral.c b/soc/microchip/sam/common/clk-peripheral.c new file mode 100644 index 00000000000..2fdc1117d34 --- /dev/null +++ b/soc/microchip/sam/common/clk-peripheral.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include +#include + +#include +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; +} diff --git a/soc/microchip/sam/common/clk-programmable.c b/soc/microchip/sam/common/clk-programmable.c new file mode 100644 index 00000000000..fb17512c2e1 --- /dev/null +++ b/soc/microchip/sam/common/clk-programmable.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +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; +} diff --git a/soc/microchip/sam/common/clk-sam9x60-pll.c b/soc/microchip/sam/common/clk-sam9x60-pll.c new file mode 100644 index 00000000000..0a3a00d50a7 --- /dev/null +++ b/soc/microchip/sam/common/clk-sam9x60-pll.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include +#include + +#include +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; +} diff --git a/soc/microchip/sam/common/clk-system.c b/soc/microchip/sam/common/clk-system.c new file mode 100644 index 00000000000..453408a3193 --- /dev/null +++ b/soc/microchip/sam/common/clk-system.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +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; +} diff --git a/soc/microchip/sam/common/clk-utmi.c b/soc/microchip/sam/common/clk-utmi.c new file mode 100644 index 00000000000..e2ca5f28d19 --- /dev/null +++ b/soc/microchip/sam/common/clk-utmi.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +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); +} diff --git a/soc/microchip/sam/common/pinctrl_soc.h b/soc/microchip/sam/common/pinctrl_soc.h new file mode 100644 index 00000000000..1897c6737fa --- /dev/null +++ b/soc/microchip/sam/common/pinctrl_soc.h @@ -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_ */ diff --git a/soc/microchip/sam/common/pmc.c b/soc/microchip/sam/common/pmc.c new file mode 100644 index 00000000000..33eb8edac85 --- /dev/null +++ b/soc/microchip/sam/common/pmc.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include +#include +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; +} diff --git a/soc/microchip/sam/common/pmc.h b/soc/microchip/sam/common/pmc.h new file mode 100644 index 00000000000..a46238f509e --- /dev/null +++ b/soc/microchip/sam/common/pmc.h @@ -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 +#include +#include + +#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_ */ diff --git a/soc/microchip/sam/common/sama7g5.c b/soc/microchip/sam/common/sama7g5.c new file mode 100644 index 00000000000..dbcb0a07d09 --- /dev/null +++ b/soc/microchip/sam/common/sama7g5.c @@ -0,0 +1,1241 @@ +/* + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(pmc_setup, CONFIG_CLOCK_CONTROL_LOG_LEVEL); + +#define nck(a) (a[ARRAY_SIZE(a) - 1].id + 1) + +#define PMC_INIT_TABLE(_table, _count) \ + do { \ + uint8_t _i; \ + for (_i = 0; _i < (_count); _i++) { \ + (_table)[_i] = _i; \ + } \ + } while (0) + +#define PMC_FILL_TABLE(_to, _from, _count) \ + do { \ + uint8_t _i; \ + for (_i = 0; _i < (_count); _i++) { \ + (_to)[_i] = (_from)[_i]; \ + } \ + } while (0) + +static struct k_spinlock pmc_pll_lock; +static struct k_spinlock pmc_mck0_lock; +static struct k_spinlock pmc_mckX_lock; +static struct k_spinlock pmc_pcr_lock; + +/* + * PLL clocks identifiers + * @PLL_ID_CPU: CPU PLL identifier + * @PLL_ID_SYS: System PLL identifier + * @PLL_ID_DDR: DDR PLL identifier + * @PLL_ID_IMG: Image subsystem PLL identifier + * @PLL_ID_BAUD: Baud PLL identifier + * @PLL_ID_AUDIO: Audio PLL identifier + * @PLL_ID_ETH: Ethernet PLL identifier + */ +enum pll_ids { + PLL_ID_CPU, + PLL_ID_SYS, + PLL_ID_DDR, + PLL_ID_IMG, + PLL_ID_BAUD, + PLL_ID_AUDIO, + PLL_ID_ETH, + PLL_ID_MAX, +}; + +/* + * PLL component identifier + * @PLL_COMPID_FRAC: Fractional PLL component identifier + * @PLL_COMPID_DIV0: 1st PLL divider component identifier + * @PLL_COMPID_DIV1: 2nd PLL divider component identifier + */ +enum pll_component_id { + PLL_COMPID_FRAC, + PLL_COMPID_DIV0, + PLL_COMPID_DIV1, + PLL_COMPID_MAX, +}; + +/* + * PLL type identifiers + * @PLL_TYPE_FRAC: fractional PLL identifier + * @PLL_TYPE_DIV: divider PLL identifier + */ +enum pll_type { + PLL_TYPE_FRAC, + PLL_TYPE_DIV, +}; + +/* Layout for fractional PLLs. */ +static const struct clk_pll_layout pll_layout_frac = { + .mul_mask = GENMASK(31, 24), + .frac_mask = GENMASK(21, 0), + .mul_shift = 24, + .frac_shift = 0, +}; + +/* Layout for DIVPMC dividers. */ +static const struct clk_pll_layout pll_layout_divpmc = { + .div_mask = GENMASK(7, 0), + .endiv_mask = BIT(29), + .div_shift = 0, + .endiv_shift = 29, +}; + +/* Layout for DIVIO dividers. */ +static const struct clk_pll_layout pll_layout_divio = { + .div_mask = GENMASK(19, 12), + .endiv_mask = BIT(30), + .div_shift = 12, + .endiv_shift = 30, +}; + +/* + * CPU PLL output range. + * Notice: The upper limit has been setup to 1000000002 due to hardware + * block which cannot output exactly 1GHz. + */ +static const struct clk_range cpu_pll_outputs[] = { + { .min = 2343750, .max = 1000000002 }, +}; + +/* PLL output range. */ +static const struct clk_range pll_outputs[] = { + { .min = 2343750, .max = 1200000000 }, +}; + +/* Fractional PLL core output range. */ +static const struct clk_range core_outputs[] = { + { .min = 600000000, .max = 1200000000 }, +}; + +/* CPU PLL characteristics. */ +static const struct clk_pll_characteristics cpu_pll_characteristics = { + .input = { .min = 12000000, .max = 50000000 }, + .num_output = ARRAY_SIZE(cpu_pll_outputs), + .output = cpu_pll_outputs, + .core_output = core_outputs, +}; + +/* PLL characteristics. */ +static const struct clk_pll_characteristics pll_characteristics = { + .input = { .min = 12000000, .max = 50000000 }, + .num_output = ARRAY_SIZE(pll_outputs), + .output = pll_outputs, + .core_output = core_outputs, +}; + +/* + * SAMA7G5 PLL possible parents + * @SAMA7G5_PLL_PARENT_MAINCK: MAINCK is PLL a parent + * @SAMA7G5_PLL_PARENT_MAIN_XTAL: MAIN XTAL is a PLL parent + * @SAMA7G5_PLL_PARENT_FRACCK: Frac PLL is a PLL parent (for PLL dividers) + */ +enum sama7g5_pll_parent { + SAMA7G5_PLL_PARENT_MAINCK, + SAMA7G5_PLL_PARENT_MAIN_XTAL, + SAMA7G5_PLL_PARENT_FRACCK, +}; + +/* + * PLL clocks description + * @n: clock name + * @l: clock layout + * @c: clock characteristics + * @clk: pointer to clk + * @t: clock type + * @f: clock flags + * @p: clock parent + * @eid: export index in sama7g5->chws[] array + * @safe_div: intermediate divider need to be set on PRE_RATE_CHANGE + * notification + */ +static struct sama7g5_pll { + const char *n; + const struct clk_pll_layout *l; + const struct clk_pll_characteristics *c; + struct device *clk; + unsigned long f; + enum sama7g5_pll_parent p; + uint8_t t; + uint8_t eid; + uint8_t safe_div; +} sama7g5_plls[][PLL_COMPID_MAX] = { + [PLL_ID_CPU] = { + [PLL_COMPID_FRAC] = { + .n = "cpupll_fracck", + .p = SAMA7G5_PLL_PARENT_MAINCK, + .l = &pll_layout_frac, + .c = &cpu_pll_characteristics, + .t = PLL_TYPE_FRAC, + }, + + [PLL_COMPID_DIV0] = { + .n = "cpupll_divpmcck", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divpmc, + .c = &cpu_pll_characteristics, + .t = PLL_TYPE_DIV, + .eid = PMC_CPUPLL, + /* + * Safe div=15 should be safe even for switching b/w 1GHz and + * 90MHz (frac pll might go up to 1.2GHz). + */ + .safe_div = 15, + }, + }, + + [PLL_ID_SYS] = { + [PLL_COMPID_FRAC] = { + .n = "syspll_fracck", + .p = SAMA7G5_PLL_PARENT_MAINCK, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + }, + + [PLL_COMPID_DIV0] = { + .n = "syspll_divpmcck", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divpmc, + .c = &pll_characteristics, + .t = PLL_TYPE_DIV, + .eid = PMC_SYSPLL, + }, + }, + + [PLL_ID_DDR] = { + [PLL_COMPID_FRAC] = { + .n = "ddrpll_fracck", + .p = SAMA7G5_PLL_PARENT_MAINCK, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + }, + + [PLL_COMPID_DIV0] = { + .n = "ddrpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divpmc, + .c = &pll_characteristics, + .t = PLL_TYPE_DIV, + }, + }, + + [PLL_ID_IMG] = { + [PLL_COMPID_FRAC] = { + .n = "imgpll_fracck", + .p = SAMA7G5_PLL_PARENT_MAINCK, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + }, + + [PLL_COMPID_DIV0] = { + .n = "imgpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divpmc, + .c = &pll_characteristics, + .t = PLL_TYPE_DIV, + }, + }, + + [PLL_ID_BAUD] = { + [PLL_COMPID_FRAC] = { + .n = "baudpll_fracck", + .p = SAMA7G5_PLL_PARENT_MAINCK, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + }, + + [PLL_COMPID_DIV0] = { + .n = "baudpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divpmc, + .c = &pll_characteristics, + .t = PLL_TYPE_DIV, + }, + }, + + [PLL_ID_AUDIO] = { + [PLL_COMPID_FRAC] = { + .n = "audiopll_fracck", + .p = SAMA7G5_PLL_PARENT_MAIN_XTAL, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + }, + + [PLL_COMPID_DIV0] = { + .n = "audiopll_divpmcck", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divpmc, + .c = &pll_characteristics, + .t = PLL_TYPE_DIV, + .eid = PMC_AUDIOPMCPLL, + }, + + [PLL_COMPID_DIV1] = { + .n = "audiopll_diviock", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divio, + .c = &pll_characteristics, + .t = PLL_TYPE_DIV, + .eid = PMC_AUDIOIOPLL, + }, + }, + + [PLL_ID_ETH] = { + [PLL_COMPID_FRAC] = { + .n = "ethpll_fracck", + .p = SAMA7G5_PLL_PARENT_MAIN_XTAL, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + }, + + [PLL_COMPID_DIV0] = { + .n = "ethpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_FRACCK, + .l = &pll_layout_divpmc, + .c = &pll_characteristics, + .t = PLL_TYPE_DIV, + }, + }, +}; + +/* Used to create an array entry identifying a PLL by its components. */ +#define PLL_IDS_TO_ARR_ENTRY(_id, _comp) { PLL_ID_##_id, PLL_COMPID_##_comp} + +/* + * Master clock (MCK[1..4]) description + * @n: clock name + * @ep: extra parents names array (entry formed by PLL components + * identifiers (see enum pll_component_id)) + * @clk: pointer to clk + * @ep_chg_id: index in parents array that specifies the changeable + * parent + * @ep_count: extra parents count + * @ep_mux_table: mux table for extra parents + * @id: clock id + * @eid: export index in sama7g5->chws[] array + * @c: true if clock is critical and cannot be disabled + */ +static struct { + const char *n; + struct { + int pll_id; + int pll_compid; + } ep[4]; + struct device *clk; + int ep_chg_id; + uint8_t ep_count; + uint8_t ep_mux_table[4]; + uint8_t id; + uint8_t eid; + uint8_t c; +} sama7g5_mckx[] = { + { .n = "mck0", }, /* Dummy entry for MCK0 to store hw in probe. */ + { .n = "mck1", + .id = 1, + .ep = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), }, + .ep_mux_table = { 5, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .eid = PMC_MCK1, + .c = 1, }, + + { .n = "mck2", + .id = 2, + .ep = { PLL_IDS_TO_ARR_ENTRY(DDR, DIV0), }, + .ep_mux_table = { 6, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .c = 1, }, + + { .n = "mck3", + .id = 3, + .ep = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(DDR, DIV0), + PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), }, + .ep_mux_table = { 5, 6, 7, }, + .ep_count = 3, + .ep_chg_id = 5, }, + + { .n = "mck4", + .id = 4, + .ep = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), }, + .ep_mux_table = { 5, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .c = 1, }, +}; + +/* + * System clock description + * @n: clock name + * @id: clock id + */ +static const struct { + const char *n; + uint8_t id; +} sama7g5_systemck[] = { + { .n = "pck0", .id = 8, }, + { .n = "pck1", .id = 9, }, + { .n = "pck2", .id = 10, }, + { .n = "pck3", .id = 11, }, + { .n = "pck4", .id = 12, }, + { .n = "pck5", .id = 13, }, + { .n = "pck6", .id = 14, }, + { .n = "pck7", .id = 15, }, +}; + +/* Mux table for programmable clocks. */ +static uint32_t sama7g5_prog_mux_table[] = { 0, 1, 2, 5, 6, 7, 8, 9, 10, }; + +/* + * Peripheral clock parent hw identifier (used to index in sama7g5_mckx[]) + * @PCK_PARENT_HW_MCK0: pck parent hw identifier is MCK0 + * @PCK_PARENT_HW_MCK1: pck parent hw identifier is MCK1 + * @PCK_PARENT_HW_MCK2: pck parent hw identifier is MCK2 + * @PCK_PARENT_HW_MCK3: pck parent hw identifier is MCK3 + * @PCK_PARENT_HW_MCK4: pck parent hw identifier is MCK4 + * @PCK_PARENT_HW_MAX: max identifier + */ +enum sama7g5_pck_parent_hw_id { + PCK_PARENT_HW_MCK0, + PCK_PARENT_HW_MCK1, + PCK_PARENT_HW_MCK2, + PCK_PARENT_HW_MCK3, + PCK_PARENT_HW_MCK4, + PCK_PARENT_HW_MAX, +}; + +/* + * Peripheral clock description + * @n: clock name + * @p: clock parent hw id + * @r: clock range values + * @id: clock id + * @chgp: index in parent array of the changeable parent + */ +static struct { + const char *n; + enum sama7g5_pck_parent_hw_id p; + struct clk_range r; + uint8_t chgp; + uint8_t id; +} sama7g5_periphck[] = { + { .n = "pioA_clk", .p = PCK_PARENT_HW_MCK0, .id = 11, }, + { .n = "securam_clk", .p = PCK_PARENT_HW_MCK0, .id = 18, }, + { .n = "sfr_clk", .p = PCK_PARENT_HW_MCK1, .id = 19, }, + { .n = "hsmc_clk", .p = PCK_PARENT_HW_MCK1, .id = 21, }, + { .n = "xdmac0_clk", .p = PCK_PARENT_HW_MCK1, .id = 22, }, + { .n = "xdmac1_clk", .p = PCK_PARENT_HW_MCK1, .id = 23, }, + { .n = "xdmac2_clk", .p = PCK_PARENT_HW_MCK1, .id = 24, }, + { .n = "acc_clk", .p = PCK_PARENT_HW_MCK1, .id = 25, }, + { .n = "aes_clk", .p = PCK_PARENT_HW_MCK1, .id = 27, }, + { .n = "tzaesbasc_clk", .p = PCK_PARENT_HW_MCK1, .id = 28, }, + { .n = "asrc_clk", .p = PCK_PARENT_HW_MCK1, .id = 30, .r = { .max = 200000000, }, }, + { .n = "cpkcc_clk", .p = PCK_PARENT_HW_MCK0, .id = 32, }, + { .n = "csi_clk", .p = PCK_PARENT_HW_MCK3, .id = 33, + .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "csi2dc_clk", .p = PCK_PARENT_HW_MCK3, .id = 34, + .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "eic_clk", .p = PCK_PARENT_HW_MCK1, .id = 37, }, + { .n = "flex0_clk", .p = PCK_PARENT_HW_MCK1, .id = 38, }, + { .n = "flex1_clk", .p = PCK_PARENT_HW_MCK1, .id = 39, }, + { .n = "flex2_clk", .p = PCK_PARENT_HW_MCK1, .id = 40, }, + { .n = "flex3_clk", .p = PCK_PARENT_HW_MCK1, .id = 41, }, + { .n = "flex4_clk", .p = PCK_PARENT_HW_MCK1, .id = 42, }, + { .n = "flex5_clk", .p = PCK_PARENT_HW_MCK1, .id = 43, }, + { .n = "flex6_clk", .p = PCK_PARENT_HW_MCK1, .id = 44, }, + { .n = "flex7_clk", .p = PCK_PARENT_HW_MCK1, .id = 45, }, + { .n = "flex8_clk", .p = PCK_PARENT_HW_MCK1, .id = 46, }, + { .n = "flex9_clk", .p = PCK_PARENT_HW_MCK1, .id = 47, }, + { .n = "flex10_clk", .p = PCK_PARENT_HW_MCK1, .id = 48, }, + { .n = "flex11_clk", .p = PCK_PARENT_HW_MCK1, .id = 49, }, + { .n = "gmac0_clk", .p = PCK_PARENT_HW_MCK1, .id = 51, }, + { .n = "gmac1_clk", .p = PCK_PARENT_HW_MCK1, .id = 52, }, + { .n = "icm_clk", .p = PCK_PARENT_HW_MCK1, .id = 55, }, + { .n = "isc_clk", .p = PCK_PARENT_HW_MCK3, .id = 56, + .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "i2smcc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 57, .r = { .max = 200000000, }, }, + { .n = "i2smcc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 58, .r = { .max = 200000000, }, }, + { .n = "matrix_clk", .p = PCK_PARENT_HW_MCK1, .id = 60, }, + { .n = "mcan0_clk", .p = PCK_PARENT_HW_MCK1, .id = 61, .r = { .max = 200000000, }, }, + { .n = "mcan1_clk", .p = PCK_PARENT_HW_MCK1, .id = 62, .r = { .max = 200000000, }, }, + { .n = "mcan2_clk", .p = PCK_PARENT_HW_MCK1, .id = 63, .r = { .max = 200000000, }, }, + { .n = "mcan3_clk", .p = PCK_PARENT_HW_MCK1, .id = 64, .r = { .max = 200000000, }, }, + { .n = "mcan4_clk", .p = PCK_PARENT_HW_MCK1, .id = 65, .r = { .max = 200000000, }, }, + { .n = "mcan5_clk", .p = PCK_PARENT_HW_MCK1, .id = 66, .r = { .max = 200000000, }, }, + { .n = "pdmc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 68, .r = { .max = 200000000, }, }, + { .n = "pdmc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 69, .r = { .max = 200000000, }, }, + { .n = "pit64b0_clk", .p = PCK_PARENT_HW_MCK1, .id = 70, }, + { .n = "pit64b1_clk", .p = PCK_PARENT_HW_MCK1, .id = 71, }, + { .n = "pit64b2_clk", .p = PCK_PARENT_HW_MCK1, .id = 72, }, + { .n = "pit64b3_clk", .p = PCK_PARENT_HW_MCK1, .id = 73, }, + { .n = "pit64b4_clk", .p = PCK_PARENT_HW_MCK1, .id = 74, }, + { .n = "pit64b5_clk", .p = PCK_PARENT_HW_MCK1, .id = 75, }, + { .n = "pwm_clk", .p = PCK_PARENT_HW_MCK1, .id = 77, }, + { .n = "qspi0_clk", .p = PCK_PARENT_HW_MCK1, .id = 78, }, + { .n = "qspi1_clk", .p = PCK_PARENT_HW_MCK1, .id = 79, }, + { .n = "sdmmc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 80, }, + { .n = "sdmmc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 81, }, + { .n = "sdmmc2_clk", .p = PCK_PARENT_HW_MCK1, .id = 82, }, + { .n = "sha_clk", .p = PCK_PARENT_HW_MCK1, .id = 83, }, + { .n = "spdifrx_clk", .p = PCK_PARENT_HW_MCK1, .id = 84, .r = { .max = 200000000, }, }, + { .n = "spdiftx_clk", .p = PCK_PARENT_HW_MCK1, .id = 85, .r = { .max = 200000000, }, }, + { .n = "ssc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 86, .r = { .max = 200000000, }, }, + { .n = "ssc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 87, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch0_clk", .p = PCK_PARENT_HW_MCK1, .id = 88, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch1_clk", .p = PCK_PARENT_HW_MCK1, .id = 89, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch2_clk", .p = PCK_PARENT_HW_MCK1, .id = 90, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch0_clk", .p = PCK_PARENT_HW_MCK1, .id = 91, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch1_clk", .p = PCK_PARENT_HW_MCK1, .id = 92, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch2_clk", .p = PCK_PARENT_HW_MCK1, .id = 93, .r = { .max = 200000000, }, }, + { .n = "tcpca_clk", .p = PCK_PARENT_HW_MCK1, .id = 94, }, + { .n = "tcpcb_clk", .p = PCK_PARENT_HW_MCK1, .id = 95, }, + { .n = "tdes_clk", .p = PCK_PARENT_HW_MCK1, .id = 96, }, + { .n = "trng_clk", .p = PCK_PARENT_HW_MCK1, .id = 97, }, + { .n = "udphsa_clk", .p = PCK_PARENT_HW_MCK1, .id = 104, }, + { .n = "udphsb_clk", .p = PCK_PARENT_HW_MCK1, .id = 105, }, + { .n = "uhphs_clk", .p = PCK_PARENT_HW_MCK1, .id = 106, }, +}; + +/* + * Generic clock description + * @n: clock name + * @pp: PLL parents (entry formed by PLL components identifiers + * (see enum pll_component_id)) + * @pp_mux_table: PLL parents mux table + * @r: clock output range + * @pp_chg_id: id in parent array of changeable PLL parent + * @pp_count: PLL parents count + * @id: clock id + */ +static const struct { + const char *n; + struct { + int pll_id; + int pll_compid; + } pp[8]; + const char pp_mux_table[8]; + struct clk_range r; + int pp_chg_id; + uint8_t pp_count; + uint8_t id; +} sama7g5_gck[] = { + { .n = "adc_gclk", + .id = 26, + .r = { .max = 100000000, }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 7, 9, }, + .pp_count = 3, + .pp_chg_id = INT_MIN, }, + + { .n = "asrc_gclk", + .id = 30, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 9, }, + .pp_count = 1, + .pp_chg_id = 3, }, + + { .n = "csi_gclk", + .id = 33, + .r = { .max = 27000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(DDR, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), }, + .pp_mux_table = { 6, 7, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex0_gclk", + .id = 38, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex1_gclk", + .id = 39, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex2_gclk", + .id = 40, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex3_gclk", + .id = 41, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex4_gclk", + .id = 42, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex5_gclk", + .id = 43, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex6_gclk", + .id = 44, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex7_gclk", + .id = 45, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex8_gclk", + .id = 46, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex9_gclk", + .id = 47, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex10_gclk", + .id = 48, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex11_gclk", + .id = 49, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac0_gclk", + .id = 51, + .r = { .max = 125000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 10, }, + .pp_count = 1, + .pp_chg_id = 3, }, + + { .n = "gmac1_gclk", + .id = 52, + .r = { .max = 50000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 10, }, + .pp_count = 1, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac0_tsu_gclk", + .id = 53, + .r = { .max = 300000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 9, 10, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac1_tsu_gclk", + .id = 54, + .r = { .max = 300000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 9, 10, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "i2smcc0_gclk", + .id = 57, + .r = { .max = 100000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "i2smcc1_gclk", + .id = 58, + .r = { .max = 100000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "mcan0_gclk", + .id = 61, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan1_gclk", + .id = 62, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan2_gclk", + .id = 63, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan3_gclk", + .id = 64, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan4_gclk", + .id = 65, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan5_gclk", + .id = 66, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pdmc0_gclk", + .id = 68, + .r = { .max = 50000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pdmc1_gclk", + .id = 69, + .r = { .max = 50000000, }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b0_gclk", + .id = 70, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b1_gclk", + .id = 71, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b2_gclk", + .id = 72, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b3_gclk", + .id = 73, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b4_gclk", + .id = 74, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b5_gclk", + .id = 75, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "qspi0_gclk", + .id = 78, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "qspi1_gclk", + .id = 79, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "sdmmc0_gclk", + .id = 80, + .r = { .max = 208000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "sdmmc1_gclk", + .id = 81, + .r = { .max = 208000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "sdmmc2_gclk", + .id = 82, + .r = { .max = 208000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "spdifrx_gclk", + .id = 84, + .r = { .max = 150000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "spdiftx_gclk", + .id = 85, + .r = { .max = 25000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "tcb0_ch0_gclk", + .id = 88, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "tcb1_ch0_gclk", + .id = 91, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 5, 7, 8, 9, 10, }, + .pp_count = 5, + .pp_chg_id = INT_MIN, }, + + { .n = "tcpca_gclk", + .id = 94, + .r = { .max = 32768, }, + .pp_chg_id = INT_MIN, }, + + { .n = "tcpcb_gclk", + .id = 95, + .r = { .max = 32768, }, + .pp_chg_id = INT_MIN, }, +}; + +/* MCK0 characteristics. */ +static const struct clk_master_characteristics mck0_characteristics = { + .output = { .min = 32768, .max = 200000000 }, + .divisors = { 1, 2, 4, 3, 5 }, + .have_div3_pres = 1, +}; + +/* MCK0 layout. */ +static const struct clk_master_layout mck0_layout = { + .mask = 0x773, + .pres_shift = 4, + .offset = 0x28, +}; + +/* Programmable clock layout. */ +static const struct clk_programmable_layout programmable_layout = { + .pres_mask = 0xff, + .pres_shift = 8, + .css_mask = 0x1f, + .have_slck_mck = 0, + .is_pres_direct = 1, +}; + +/* Peripheral clock layout. */ +static const struct clk_pcr_layout sama7g5_pcr_layout = { + .offset = 0x88, + .cmd = BIT(31), + .gckcss_mask = GENMASK(12, 8), + .pid_mask = GENMASK(6, 0), +}; + +enum clock_count { + CLK_CNT_SYSTEM = 15 + 1, + CLK_CNT_PERIPH = 106 + 1, + CLK_CNT_GCK = 95 + 1, +}; + +static struct device *pmc_table[PMC_MCK1 + 1 + CLK_CNT_SYSTEM + CLK_CNT_PERIPH + CLK_CNT_GCK + 8]; + +void sam_pmc_setup(const struct device *dev) +{ + const struct sam_pmc_cfg *cfg = dev->config; + struct sam_pmc_data *data = dev->data; + const struct device *main_xtal = cfg->main_xtal; + const struct device *td_slck = cfg->td_slck; + const struct device *md_slck = cfg->md_slck; + + void *const regmap = cfg->reg; + const struct device *parents[10]; + struct pmc_data *sama7g5_pmc; + struct device *clk, *main_rc, *main_osc; + uint32_t mux_table[8]; + + int ret = 0; + uint32_t rate, bypass; + int i, j; + + + if (!td_slck || !md_slck || !main_xtal || !regmap) { + LOG_ERR("Incorrect parameters."); + return; + } + + if (CLK_CNT_SYSTEM != nck(sama7g5_systemck) || + CLK_CNT_PERIPH != nck(sama7g5_periphck) || + CLK_CNT_GCK != nck(sama7g5_gck)) { + LOG_ERR("Incorrect definitions could make array for pmc clocks not enough"); + return; + } + sama7g5_pmc = pmc_data_allocate(PMC_MCK1 + 1, + nck(sama7g5_systemck), + nck(sama7g5_periphck), + nck(sama7g5_gck), 8, &pmc_table[0]); + if (!sama7g5_pmc) { + LOG_ERR("allocate PMC data failed."); + return; + } + data->pmc = sama7g5_pmc; + + ret = clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000, &main_rc); + if (ret) { + LOG_ERR("Register clock main_rc_osc failed."); + return; + } + + if (clock_control_get_rate(main_xtal, NULL, &rate)) { + LOG_ERR("get clock rate of main_xtal failed."); + return; + } + + bypass = 0; + ret = clk_register_main_osc(regmap, "main_osc", bypass, rate, &main_osc); + if (ret) { + LOG_ERR("Register clock main_osc failed."); + return; + } + + parents[0] = main_rc; + parents[1] = main_osc; + ret = clk_register_main(regmap, "mainck", parents, 2, &clk); + if (ret) { + LOG_ERR("Register clock mainck failed."); + return; + } + + sama7g5_pmc->chws[PMC_MAIN] = clk; + + for (i = 0; i < PLL_ID_MAX; i++) { + for (j = 0; j < PLL_COMPID_MAX; j++) { + const struct device *parent_hw; + + if (!sama7g5_plls[i][j].n) { + continue; + } + + switch (sama7g5_plls[i][j].t) { + case PLL_TYPE_FRAC: + switch (sama7g5_plls[i][j].p) { + case SAMA7G5_PLL_PARENT_MAINCK: + parent_hw = sama7g5_pmc->chws[PMC_MAIN]; + break; + case SAMA7G5_PLL_PARENT_MAIN_XTAL: + parent_hw = main_xtal; + break; + default: + /* Should not happen. */ + parent_hw = NULL; + break; + } + + ret = sam9x60_clk_register_frac_pll(regmap, + &pmc_pll_lock, + sama7g5_plls[i][j].n, + parent_hw, i, + sama7g5_plls[i][j].c, + sama7g5_plls[i][j].l, + &clk); + break; + + case PLL_TYPE_DIV: + ret = sam9x60_clk_register_div_pll(regmap, + &pmc_pll_lock, + sama7g5_plls[i][j].n, + sama7g5_plls[i][0].clk, i, + sama7g5_plls[i][j].c, + sama7g5_plls[i][j].l, + &clk); + break; + + default: + continue; + } + + if (ret) { + LOG_ERR("Register clock %s failed.", sama7g5_plls[i][j].n); + return; + } + + sama7g5_plls[i][j].clk = clk; + if (sama7g5_plls[i][j].eid) { + sama7g5_pmc->chws[sama7g5_plls[i][j].eid] = clk; + } + } + } + + ret = clk_register_master_div(regmap, "mck0", + sama7g5_plls[PLL_ID_CPU][1].clk, + &mck0_layout, &mck0_characteristics, + &pmc_mck0_lock, 5, &clk); + if (ret) { + LOG_ERR("Register MCK0 clock failed."); + return; + } + sama7g5_mckx[PCK_PARENT_HW_MCK0].clk = sama7g5_pmc->chws[PMC_MCK] = clk; + + parents[0] = md_slck; + parents[1] = td_slck; + parents[2] = sama7g5_pmc->chws[PMC_MAIN]; + for (i = PCK_PARENT_HW_MCK1; i < ARRAY_SIZE(sama7g5_mckx); i++) { + uint8_t num_parents = 3 + sama7g5_mckx[i].ep_count; + struct device *tmp_parent_hws[8]; + + if (num_parents > ARRAY_SIZE(mux_table)) { + LOG_ERR("Array for mux table not enough"); + return; + } + + PMC_INIT_TABLE(mux_table, 3); + PMC_FILL_TABLE(&mux_table[3], sama7g5_mckx[i].ep_mux_table, + sama7g5_mckx[i].ep_count); + for (j = 0; j < sama7g5_mckx[i].ep_count; j++) { + uint8_t pll_id = sama7g5_mckx[i].ep[j].pll_id; + uint8_t pll_compid = sama7g5_mckx[i].ep[j].pll_compid; + + tmp_parent_hws[j] = sama7g5_plls[pll_id][pll_compid].clk; + } + PMC_FILL_TABLE(&parents[3], tmp_parent_hws, + sama7g5_mckx[i].ep_count); + + ret = clk_register_master(regmap, sama7g5_mckx[i].n, + num_parents, parents, + mux_table, + &pmc_mckX_lock, sama7g5_mckx[i].id, + &clk); + if (ret) { + LOG_ERR("Register MCK%d clock failed.", i); + return; + } + + sama7g5_mckx[i].clk = clk; + if (sama7g5_mckx[i].eid) { + sama7g5_pmc->chws[sama7g5_mckx[i].eid] = clk; + } + } + + ret = clk_register_utmi(regmap, "utmick", main_xtal, &clk); + if (ret) { + LOG_ERR("Register UTMI clock failed."); + return; + } + + sama7g5_pmc->chws[PMC_UTMI] = clk; + + parents[0] = md_slck; + parents[1] = td_slck; + parents[2] = sama7g5_pmc->chws[PMC_MAIN]; + parents[3] = sama7g5_plls[PLL_ID_SYS][PLL_COMPID_DIV0].clk; + parents[4] = sama7g5_plls[PLL_ID_DDR][PLL_COMPID_DIV0].clk; + parents[5] = sama7g5_plls[PLL_ID_IMG][PLL_COMPID_DIV0].clk; + parents[6] = sama7g5_plls[PLL_ID_BAUD][PLL_COMPID_DIV0].clk; + parents[7] = sama7g5_plls[PLL_ID_AUDIO][PLL_COMPID_DIV0].clk; + parents[8] = sama7g5_plls[PLL_ID_ETH][PLL_COMPID_DIV0].clk; + for (i = 0; i < 8; i++) { + char name[6]; + + snprintf(name, sizeof(name), "prog%d", i); + + ret = clk_register_programmable(regmap, name, parents, + 9, i, + &programmable_layout, + sama7g5_prog_mux_table, &clk); + if (ret) { + LOG_ERR("Register programmable clock %d failed.", i); + return; + } + + sama7g5_pmc->pchws[i] = clk; + } + + for (i = 0; i < ARRAY_SIZE(sama7g5_systemck); i++) { + ret = clk_register_system(regmap, sama7g5_systemck[i].n, + sama7g5_pmc->pchws[i], + sama7g5_systemck[i].id, &clk); + if (ret) { + LOG_ERR("Register system clock %d failed.", i); + return; + } + + sama7g5_pmc->shws[sama7g5_systemck[i].id] = clk; + } + + for (i = 0; i < ARRAY_SIZE(sama7g5_periphck); i++) { + clk = sama7g5_mckx[sama7g5_periphck[i].p].clk; + ret = clk_register_peripheral(regmap, &pmc_pcr_lock, + &sama7g5_pcr_layout, + sama7g5_periphck[i].n, + clk, + sama7g5_periphck[i].id, + &sama7g5_periphck[i].r, + &clk); + if (ret) { + LOG_ERR("Register peripheral clock failed."); + return; + } + sama7g5_pmc->phws[sama7g5_periphck[i].id] = clk; + } + + parents[0] = md_slck; + parents[1] = td_slck; + parents[2] = sama7g5_pmc->chws[PMC_MAIN]; + + for (i = 0; i < ARRAY_SIZE(sama7g5_gck); i++) { + uint8_t num_parents = 3 + sama7g5_gck[i].pp_count; + struct device *tmp_parent_hws[8]; + + if (num_parents > ARRAY_SIZE(mux_table)) { + LOG_ERR("Array for mux table not enough"); + return; + } + + PMC_INIT_TABLE(mux_table, 3); + PMC_FILL_TABLE(&mux_table[3], sama7g5_gck[i].pp_mux_table, + sama7g5_gck[i].pp_count); + for (j = 0; j < sama7g5_gck[i].pp_count; j++) { + uint8_t pll_id = sama7g5_gck[i].pp[j].pll_id; + uint8_t pll_compid = sama7g5_gck[i].pp[j].pll_compid; + + tmp_parent_hws[j] = sama7g5_plls[pll_id][pll_compid].clk; + } + PMC_FILL_TABLE(&parents[3], tmp_parent_hws, sama7g5_gck[i].pp_count); + + ret = clk_register_generated(regmap, &pmc_pcr_lock, + &sama7g5_pcr_layout, + sama7g5_gck[i].n, + parents, mux_table, + num_parents, + sama7g5_gck[i].id, + &sama7g5_gck[i].r, + sama7g5_gck[i].pp_chg_id, &clk); + if (ret) { + LOG_ERR("Register generated clock failed."); + return; + } + sama7g5_pmc->ghws[sama7g5_gck[i].id] = clk; + } + + LOG_DBG("Register PMC clocks successfully."); +}