blob: f721ea466a03b9823c1624c84b3a4428bbb74b77 [file] [log] [blame]
/*
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <pmc.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/spinlock.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(clk_mck, CONFIG_CLOCK_CONTROL_LOG_LEVEL);
#define MASTER_PRES_MASK 0x7
#define MASTER_PRES_MAX MASTER_PRES_MASK
#define MASTER_DIV_SHIFT 8
#define MASTER_DIV_MASK 0x7
#define PMC_MCR_CSS_SHIFT 16
#define MASTER_MAX_ID 4
#define to_clk_master(ptr) CONTAINER_OF(ptr, struct clk_master, clk)
struct clk_master {
pmc_registers_t *pmc;
struct device clk;
struct device *parents[6];
struct k_spinlock *lock;
const struct clk_master_layout *layout;
const struct clk_master_characteristics *characteristics;
uint32_t mux_table[6];
uint32_t mckr;
uint8_t id;
uint8_t num_parents;
uint8_t parent;
uint8_t div;
};
static struct clk_master clocks_master[5];
static uint32_t clocks_master_idx;
static inline bool clk_master_ready(struct clk_master *master)
{
uint32_t bit = master->id ? PMC_SR_MCKXRDY_Msk : PMC_SR_MCKRDY_Msk;
uint32_t status;
status = master->pmc->PMC_SR;
return !!(status & bit);
}
static int clk_master_div_on(const struct device *dev, clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct clk_master *master = to_clk_master(dev);
k_spinlock_key_t key;
key = k_spin_lock(master->lock);
while (!clk_master_ready(master)) {
k_busy_wait(1);
}
k_spin_unlock(master->lock, key);
return 0;
}
static enum clock_control_status clk_master_div_get_status(const struct device *dev,
clock_control_subsys_t sys)
{
ARG_UNUSED(sys);
struct clk_master *master = to_clk_master(dev);
k_spinlock_key_t key;
bool status;
key = k_spin_lock(master->lock);
status = clk_master_ready(master);
k_spin_unlock(master->lock, key);
return status ? CLOCK_CONTROL_STATUS_ON : CLOCK_CONTROL_STATUS_OFF;
}
static int clk_master_div_get_rate(const struct device *dev,
clock_control_subsys_t sys, uint32_t *rate)
{
ARG_UNUSED(sys);
struct clk_master *master = to_clk_master(dev);
uint32_t retval = 0;
retval = clock_control_get_rate(master->parents[master->parent], NULL, rate);
if (!retval) {
*rate /= master->div;
}
LOG_DBG("%s, rate %d", dev->name, *rate);
return retval;
}
static DEVICE_API(clock_control, master_div_api) = {
.on = clk_master_div_on,
.get_rate = clk_master_div_get_rate,
.get_status = clk_master_div_get_status,
};
int clk_register_master_div(pmc_registers_t *const pmc, const char *name,
const struct device *parent, const struct clk_master_layout *layout,
const struct clk_master_characteristics *characteristics,
struct k_spinlock *lock, uint32_t safe_div, struct device **clk)
{
struct clk_master *master;
k_spinlock_key_t key;
uint32_t mckr;
if (!name || !lock) {
return -EINVAL;
}
master = &clocks_master[clocks_master_idx++];
if (clocks_master_idx > ARRAY_SIZE(clocks_master)) {
LOG_ERR("Array for master clock not enough");
return -ENOMEM;
}
*clk = &master->clk;
(*clk)->name = name;
(*clk)->api = &master_div_api;
memcpy(master->parents, &parent, sizeof(struct device *) * 1);
master->num_parents = 1;
master->parent = 0;
master->layout = layout;
master->characteristics = characteristics;
master->pmc = pmc;
master->lock = lock;
key = k_spin_lock(master->lock);
regmap_read((void *)master->pmc, master->layout->offset, &mckr);
k_spin_unlock(master->lock, key);
mckr &= layout->mask;
mckr = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
master->div = characteristics->divisors[mckr];
return 0;
}
static int clk_mck_get_rate(const struct device *dev, clock_control_subsys_t sys, uint32_t *rate)
{
ARG_UNUSED(sys);
struct clk_master *master = to_clk_master(dev);
int retval = 0;
uint32_t mcr;
const struct device *parent;
k_spinlock_key_t key;
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;
}
}
key = k_spin_lock(master->lock);
master->pmc->PMC_MCR = master->id;
mcr = master->pmc->PMC_MCR;
master->div = FIELD_GET(PMC_MCR_DIV_Msk, mcr);
k_spin_unlock(master->lock, key);
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;
}