u-boot/drivers/clk/at91/clk-generated.c
Wenyou Yang 6cadaa046b clk: at91: Improve the clock implementation
For the peripheral clock, provide the clock ops for the clock
provider, such as spi0_clk. The .of_xlate is to get the clk->id,
the .enable is to enable the spi0 peripheral clock, the .get_rate
is to get the clock frequency.

The driver for periph32ck node is responsible for recursively
binding its children as clk devices, not provide the clock ops.

So do the generated clock and system clock.

Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
Acked-by: Stephen Warren <swarren@nvidia.com>
2016-10-28 18:37:14 +02:00

177 lines
4.1 KiB
C

/*
* Copyright (C) 2016 Atmel Corporation
* Wenyou.Yang <wenyou.yang@atmel.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <clk-uclass.h>
#include <dm/device.h>
#include <linux/io.h>
#include <mach/at91_pmc.h>
#include "pmc.h"
DECLARE_GLOBAL_DATA_PTR;
#define GENERATED_SOURCE_MAX 6
#define GENERATED_MAX_DIV 255
/**
* generated_clk_bind() - for the generated clock driver
* Recursively bind its children as clk devices.
*
* @return: 0 on success, or negative error code on failure
*/
static int generated_clk_bind(struct udevice *dev)
{
return at91_clk_sub_device_bind(dev, "generic-clk");
}
static const struct udevice_id generated_clk_match[] = {
{ .compatible = "atmel,sama5d2-clk-generated" },
{}
};
U_BOOT_DRIVER(generated_clk) = {
.name = "generated-clk",
.id = UCLASS_MISC,
.of_match = generated_clk_match,
.bind = generated_clk_bind,
};
/*-------------------------------------------------------------*/
struct generic_clk_priv {
u32 num_parents;
};
static ulong generic_clk_get_rate(struct clk *clk)
{
struct pmc_platdata *plat = dev_get_platdata(clk->dev);
struct at91_pmc *pmc = plat->reg_base;
struct clk parent;
ulong clk_rate;
u32 tmp, gckdiv;
u8 parent_id;
int ret;
writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
tmp = readl(&pmc->pcr);
parent_id = (tmp >> AT91_PMC_PCR_GCKCSS_OFFSET) &
AT91_PMC_PCR_GCKCSS_MASK;
gckdiv = (tmp >> AT91_PMC_PCR_GCKDIV_OFFSET) & AT91_PMC_PCR_GCKDIV_MASK;
ret = clk_get_by_index(dev_get_parent(clk->dev), parent_id, &parent);
if (ret)
return 0;
clk_rate = clk_get_rate(&parent) / (gckdiv + 1);
clk_free(&parent);
return clk_rate;
}
static ulong generic_clk_set_rate(struct clk *clk, ulong rate)
{
struct pmc_platdata *plat = dev_get_platdata(clk->dev);
struct at91_pmc *pmc = plat->reg_base;
struct generic_clk_priv *priv = dev_get_priv(clk->dev);
struct clk parent, best_parent;
ulong tmp_rate, best_rate = rate, parent_rate;
int tmp_diff, best_diff = -1;
u32 div, best_div = 0;
u8 best_parent_id = 0;
u8 i;
u32 tmp;
int ret;
for (i = 0; i < priv->num_parents; i++) {
ret = clk_get_by_index(dev_get_parent(clk->dev), i, &parent);
if (ret)
return ret;
parent_rate = clk_get_rate(&parent);
if (IS_ERR_VALUE(parent_rate))
return parent_rate;
for (div = 1; div < GENERATED_MAX_DIV + 2; div++) {
tmp_rate = DIV_ROUND_CLOSEST(parent_rate, div);
if (rate < tmp_rate)
continue;
tmp_diff = rate - tmp_rate;
if (best_diff < 0 || best_diff > tmp_diff) {
best_rate = tmp_rate;
best_diff = tmp_diff;
best_div = div - 1;
best_parent = parent;
best_parent_id = i;
}
if (!best_diff || tmp_rate < rate)
break;
}
if (!best_diff)
break;
}
debug("GCK: best parent: %s, best_rate = %ld, best_div = %d\n",
best_parent.dev->name, best_rate, best_div);
ret = clk_enable(&best_parent);
if (ret)
return ret;
writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
tmp = readl(&pmc->pcr);
tmp &= ~(AT91_PMC_PCR_GCKDIV | AT91_PMC_PCR_GCKCSS);
tmp |= AT91_PMC_PCR_GCKCSS_(best_parent_id) |
AT91_PMC_PCR_CMD_WRITE |
AT91_PMC_PCR_GCKDIV_(best_div) |
AT91_PMC_PCR_GCKEN;
writel(tmp, &pmc->pcr);
while (!(readl(&pmc->sr) & AT91_PMC_GCKRDY))
;
return 0;
}
static struct clk_ops generic_clk_ops = {
.of_xlate = at91_clk_of_xlate,
.get_rate = generic_clk_get_rate,
.set_rate = generic_clk_set_rate,
};
static int generic_clk_ofdata_to_platdata(struct udevice *dev)
{
struct generic_clk_priv *priv = dev_get_priv(dev);
u32 cells[GENERATED_SOURCE_MAX];
u32 num_parents;
num_parents = fdtdec_get_int_array_count(gd->fdt_blob,
dev_get_parent(dev)->of_offset,
"clocks", cells,
GENERATED_SOURCE_MAX);
if (!num_parents)
return -1;
priv->num_parents = num_parents;
return 0;
}
U_BOOT_DRIVER(generic_clk) = {
.name = "generic-clk",
.id = UCLASS_CLK,
.probe = at91_clk_probe,
.ofdata_to_platdata = generic_clk_ofdata_to_platdata,
.priv_auto_alloc_size = sizeof(struct generic_clk_priv),
.platdata_auto_alloc_size = sizeof(struct pmc_platdata),
.ops = &generic_clk_ops,
};