blob: d0b14656c4d67355ae28b2425c32de8a75321616 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Programmable clock support for AT91 architectures.
*
* Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
*
* Author: Claudiu Beznea <claudiu.beznea@microchip.com>
*
* Based on drivers/clk/at91/clk-programmable.c from Linux.
*/
#include <clk-uclass.h>
#include <dm.h>
#include <linux/clk-provider.h>
#include <linux/clk/at91_pmc.h>
#include "pmc.h"
#define UBOOT_DM_CLK_AT91_PROG "at91-prog-clk"
#define PROG_ID_MAX 7
#define PROG_STATUS_MASK(id) (1 << ((id) + 8))
#define PROG_PRES(_l, _p) (((_p) >> (_l)->pres_shift) & (_l)->pres_mask)
#define PROG_MAX_RM9200_CSS 3
struct clk_programmable {
void __iomem *base;
const u32 *clk_mux_table;
const u32 *mux_table;
const struct clk_programmable_layout *layout;
u32 num_parents;
struct clk clk;
u8 id;
};
#define to_clk_programmable(_c) container_of(_c, struct clk_programmable, clk)
static ulong clk_programmable_get_rate(struct clk *clk)
{
struct clk_programmable *prog = to_clk_programmable(clk);
const struct clk_programmable_layout *layout = prog->layout;
ulong rate, parent_rate = clk_get_parent_rate(clk);
unsigned int pckr;
pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &pckr);
if (layout->is_pres_direct)
rate = parent_rate / (PROG_PRES(layout, pckr) + 1);
else
rate = parent_rate >> PROG_PRES(layout, pckr);
return rate;
}
static int clk_programmable_set_parent(struct clk *clk, struct clk *parent)
{
struct clk_programmable *prog = to_clk_programmable(clk);
const struct clk_programmable_layout *layout = prog->layout;
unsigned int mask = layout->css_mask;
int index;
index = at91_clk_mux_val_to_index(prog->clk_mux_table,
prog->num_parents, parent->id);
if (index < 0)
return index;
index = at91_clk_mux_index_to_val(prog->mux_table, prog->num_parents,
index);
if (index < 0)
return index;
if (layout->have_slck_mck)
mask |= AT91_PMC_CSSMCK_MCK;
if (index > layout->css_mask) {
if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck)
return -EINVAL;
index |= AT91_PMC_CSSMCK_MCK;
}
pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id), mask, index);
return 0;
}
static ulong clk_programmable_set_rate(struct clk *clk, ulong rate)
{
struct clk_programmable *prog = to_clk_programmable(clk);
const struct clk_programmable_layout *layout = prog->layout;
ulong parent_rate = clk_get_parent_rate(clk);
ulong div = parent_rate / rate;
int shift = 0;
if (!parent_rate || !div)
return -EINVAL;
if (layout->is_pres_direct) {
shift = div - 1;
if (shift > layout->pres_mask)
return -EINVAL;
} else {
shift = fls(div) - 1;
if (div != (1 << shift))
return -EINVAL;
if (shift >= layout->pres_mask)
return -EINVAL;
}
pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id),
layout->pres_mask << layout->pres_shift,
shift << layout->pres_shift);
if (layout->is_pres_direct)
return (parent_rate / shift + 1);
return parent_rate >> shift;
}
static const struct clk_ops programmable_ops = {
.get_rate = clk_programmable_get_rate,
.set_parent = clk_programmable_set_parent,
.set_rate = clk_programmable_set_rate,
};
struct clk *at91_clk_register_programmable(void __iomem *base, const char *name,
const char *const *parent_names, u8 num_parents, u8 id,
const struct clk_programmable_layout *layout,
const u32 *clk_mux_table, const u32 *mux_table)
{
struct clk_programmable *prog;
struct clk *clk;
u32 val, tmp;
int ret;
if (!base || !name || !parent_names || !num_parents ||
!layout || !clk_mux_table || !mux_table || id > PROG_ID_MAX)
return ERR_PTR(-EINVAL);
prog = kzalloc(sizeof(*prog), GFP_KERNEL);
if (!prog)
return ERR_PTR(-ENOMEM);
prog->id = id;
prog->layout = layout;
prog->base = base;
prog->clk_mux_table = clk_mux_table;
prog->mux_table = mux_table;
prog->num_parents = num_parents;
pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &tmp);
val = tmp & prog->layout->css_mask;
if (layout->have_slck_mck && (tmp & AT91_PMC_CSSMCK_MCK) && !val)
ret = PROG_MAX_RM9200_CSS + 1;
else
ret = at91_clk_mux_val_to_index(prog->mux_table,
prog->num_parents, val);
if (ret < 0) {
kfree(prog);
return ERR_PTR(ret);
}
clk = &prog->clk;
clk->flags = CLK_GET_RATE_NOCACHE;
ret = clk_register(clk, UBOOT_DM_CLK_AT91_PROG, name,
parent_names[ret]);
if (ret) {
kfree(prog);
clk = ERR_PTR(ret);
}
return clk;
}
U_BOOT_DRIVER(at91_prog_clk) = {
.name = UBOOT_DM_CLK_AT91_PROG,
.id = UCLASS_CLK,
.ops = &programmable_ops,
.flags = DM_FLAG_PRE_RELOC,
};
const struct clk_programmable_layout at91rm9200_programmable_layout = {
.pres_mask = 0x7,
.pres_shift = 2,
.css_mask = 0x3,
.have_slck_mck = 0,
.is_pres_direct = 0,
};
const struct clk_programmable_layout at91sam9g45_programmable_layout = {
.pres_mask = 0x7,
.pres_shift = 2,
.css_mask = 0x3,
.have_slck_mck = 1,
.is_pres_direct = 0,
};
const struct clk_programmable_layout at91sam9x5_programmable_layout = {
.pres_mask = 0x7,
.pres_shift = 4,
.css_mask = 0x7,
.have_slck_mck = 0,
.is_pres_direct = 0,
};