blob: 1840f73beeec6a8efe79ae78cfcdda66841a8d47 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 Marek Vasut <marex@denx.de>
*/
#include <malloc.h>
#include <asm/io.h>
#include <clk-uclass.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <dm/devres.h>
#include <dm/lists.h>
#include <dm/util.h>
#include <linux/bitops.h>
#include <asm/global_data.h>
#include <asm/arch/clock_manager.h>
enum socfpga_a10_clk_type {
SOCFPGA_A10_CLK_MAIN_PLL,
SOCFPGA_A10_CLK_PER_PLL,
SOCFPGA_A10_CLK_PERIP_CLK,
SOCFPGA_A10_CLK_GATE_CLK,
SOCFPGA_A10_CLK_UNKNOWN_CLK,
};
struct socfpga_a10_clk_plat {
enum socfpga_a10_clk_type type;
struct clk_bulk clks;
u32 regs;
/* Fixed divider */
u16 fix_div;
/* Control register */
u16 ctl_reg;
/* Divider register */
u16 div_reg;
u8 div_len;
u8 div_off;
/* Clock gating register */
u16 gate_reg;
u8 gate_bit;
};
static int socfpga_a10_clk_get_upstream(struct clk *clk, struct clk **upclk)
{
struct socfpga_a10_clk_plat *plat = dev_get_plat(clk->dev);
u32 reg, maxval;
if (plat->clks.count == 0)
return 0;
if (plat->clks.count == 1) {
*upclk = &plat->clks.clks[0];
return 0;
}
if (!plat->ctl_reg) {
dev_err(clk->dev, "Invalid control register\n");
return -EINVAL;
}
reg = readl(plat->regs + plat->ctl_reg);
/* Assume PLLs are ON for now */
if (plat->type == SOCFPGA_A10_CLK_MAIN_PLL) {
reg = (reg >> 8) & 0x3;
maxval = 2;
} else if (plat->type == SOCFPGA_A10_CLK_PER_PLL) {
reg = (reg >> 8) & 0x3;
maxval = 3;
} else {
reg = (reg >> 16) & 0x7;
maxval = 4;
}
if (reg > maxval) {
dev_err(clk->dev, "Invalid clock source\n");
return -EINVAL;
}
*upclk = &plat->clks.clks[reg];
return 0;
}
static int socfpga_a10_clk_endisable(struct clk *clk, bool enable)
{
struct socfpga_a10_clk_plat *plat = dev_get_plat(clk->dev);
struct clk *upclk = NULL;
int ret;
if (!enable && plat->gate_reg)
clrbits_le32(plat->regs + plat->gate_reg, BIT(plat->gate_bit));
ret = socfpga_a10_clk_get_upstream(clk, &upclk);
if (ret)
return ret;
if (upclk) {
if (enable)
clk_enable(upclk);
else
clk_disable(upclk);
}
if (enable && plat->gate_reg)
setbits_le32(plat->regs + plat->gate_reg, BIT(plat->gate_bit));
return 0;
}
static int socfpga_a10_clk_enable(struct clk *clk)
{
return socfpga_a10_clk_endisable(clk, true);
}
static int socfpga_a10_clk_disable(struct clk *clk)
{
return socfpga_a10_clk_endisable(clk, false);
}
static ulong socfpga_a10_clk_get_rate(struct clk *clk)
{
struct socfpga_a10_clk_plat *plat = dev_get_plat(clk->dev);
struct clk *upclk = NULL;
ulong rate = 0, reg, numer, denom;
int ret;
ret = socfpga_a10_clk_get_upstream(clk, &upclk);
if (ret || !upclk)
return 0;
rate = clk_get_rate(upclk);
if (plat->type == SOCFPGA_A10_CLK_MAIN_PLL) {
reg = readl(plat->regs + plat->ctl_reg + 4); /* VCO1 */
numer = reg & CLKMGR_MAINPLL_VCO1_NUMER_MSK;
denom = (reg >> CLKMGR_MAINPLL_VCO1_DENOM_LSB) &
CLKMGR_MAINPLL_VCO1_DENOM_MSK;
rate /= denom + 1;
rate *= numer + 1;
} else if (plat->type == SOCFPGA_A10_CLK_PER_PLL) {
reg = readl(plat->regs + plat->ctl_reg + 4); /* VCO1 */
numer = reg & CLKMGR_PERPLL_VCO1_NUMER_MSK;
denom = (reg >> CLKMGR_PERPLL_VCO1_DENOM_LSB) &
CLKMGR_PERPLL_VCO1_DENOM_MSK;
rate /= denom + 1;
rate *= numer + 1;
} else {
rate /= plat->fix_div;
if (plat->fix_div == 1 && plat->ctl_reg) {
reg = readl(plat->regs + plat->ctl_reg);
reg &= 0x7ff;
rate /= reg + 1;
}
if (plat->div_reg) {
reg = readl(plat->regs + plat->div_reg);
reg >>= plat->div_off;
reg &= (1 << plat->div_len) - 1;
if (plat->type == SOCFPGA_A10_CLK_PERIP_CLK)
rate /= reg + 1;
if (plat->type == SOCFPGA_A10_CLK_GATE_CLK)
rate /= 1 << reg;
}
}
return rate;
}
static struct clk_ops socfpga_a10_clk_ops = {
.enable = socfpga_a10_clk_enable,
.disable = socfpga_a10_clk_disable,
.get_rate = socfpga_a10_clk_get_rate,
};
/*
* This workaround tries to fix the massively broken generated "handoff" DT,
* which contains duplicate clock nodes without any connection to the clock
* manager DT node. Yet, those "handoff" DT nodes contain configuration of
* the fixed input clock of the Arria10 which are missing from the base DT
* for Arria10.
*
* This workaround sets up upstream clock for the fixed input clocks of the
* A10 described in the base DT such that they map to the fixed clock from
* the "handoff" DT. This does not fully match how the clock look on the
* A10, but it is the least intrusive way to fix this mess.
*/
static void socfpga_a10_handoff_workaround(struct udevice *dev)
{
struct socfpga_a10_clk_plat *plat = dev_get_plat(dev);
const void *fdt = gd->fdt_blob;
struct clk_bulk *bulk = &plat->clks;
int i, ret, offset = dev_of_offset(dev);
static const char * const socfpga_a10_fixedclk_map[] = {
"osc1", "altera_arria10_hps_eosc1",
"cb_intosc_ls_clk", "altera_arria10_hps_cb_intosc_ls",
"f2s_free_clk", "altera_arria10_hps_f2h_free",
};
if (fdt_node_check_compatible(fdt, offset, "fixed-clock"))
return;
for (i = 0; i < ARRAY_SIZE(socfpga_a10_fixedclk_map); i += 2)
if (!strcmp(dev->name, socfpga_a10_fixedclk_map[i]))
break;
if (i == ARRAY_SIZE(socfpga_a10_fixedclk_map))
return;
ret = uclass_get_device_by_name(UCLASS_CLK,
socfpga_a10_fixedclk_map[i + 1], &dev);
if (ret)
return;
bulk->count = 1;
bulk->clks = devm_kcalloc(dev, bulk->count,
sizeof(struct clk), GFP_KERNEL);
if (!bulk->clks)
return;
ret = clk_request(dev, &bulk->clks[0]);
if (ret)
free(bulk->clks);
}
static int socfpga_a10_clk_bind(struct udevice *dev)
{
const void *fdt = gd->fdt_blob;
int offset = dev_of_offset(dev);
bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
const char *name;
int ret;
for (offset = fdt_first_subnode(fdt, offset);
offset > 0;
offset = fdt_next_subnode(fdt, offset)) {
name = fdt_get_name(fdt, offset, NULL);
if (!name)
return -EINVAL;
if (!strcmp(name, "clocks")) {
offset = fdt_first_subnode(fdt, offset);
name = fdt_get_name(fdt, offset, NULL);
if (!name)
return -EINVAL;
}
/* Filter out supported sub-clock */
if (fdt_node_check_compatible(fdt, offset,
"altr,socfpga-a10-pll-clock") &&
fdt_node_check_compatible(fdt, offset,
"altr,socfpga-a10-perip-clk") &&
fdt_node_check_compatible(fdt, offset,
"altr,socfpga-a10-gate-clk") &&
fdt_node_check_compatible(fdt, offset, "fixed-clock"))
continue;
if (pre_reloc_only &&
!ofnode_pre_reloc(offset_to_ofnode(offset)))
continue;
ret = device_bind_driver_to_node(dev, "clk-a10", name,
offset_to_ofnode(offset),
NULL);
if (ret)
return ret;
}
return 0;
}
static int socfpga_a10_clk_probe(struct udevice *dev)
{
struct socfpga_a10_clk_plat *plat = dev_get_plat(dev);
struct socfpga_a10_clk_plat *pplat;
struct udevice *pdev;
const void *fdt = gd->fdt_blob;
int offset = dev_of_offset(dev);
clk_get_bulk(dev, &plat->clks);
socfpga_a10_handoff_workaround(dev);
if (!fdt_node_check_compatible(fdt, offset, "altr,clk-mgr")) {
plat->regs = dev_read_addr(dev);
} else {
pdev = dev_get_parent(dev);
if (!pdev)
return -ENODEV;
pplat = dev_get_plat(pdev);
if (!pplat)
return -EINVAL;
plat->ctl_reg = dev_read_u32_default(dev, "reg", 0x0);
plat->regs = pplat->regs;
}
if (!fdt_node_check_compatible(fdt, offset,
"altr,socfpga-a10-pll-clock")) {
/* Main PLL has 3 upstream clock */
if (plat->clks.count == 3)
plat->type = SOCFPGA_A10_CLK_MAIN_PLL;
else
plat->type = SOCFPGA_A10_CLK_PER_PLL;
} else if (!fdt_node_check_compatible(fdt, offset,
"altr,socfpga-a10-perip-clk")) {
plat->type = SOCFPGA_A10_CLK_PERIP_CLK;
} else if (!fdt_node_check_compatible(fdt, offset,
"altr,socfpga-a10-gate-clk")) {
plat->type = SOCFPGA_A10_CLK_GATE_CLK;
} else {
plat->type = SOCFPGA_A10_CLK_UNKNOWN_CLK;
}
return 0;
}
static int socfpga_a10_of_to_plat(struct udevice *dev)
{
struct socfpga_a10_clk_plat *plat = dev_get_plat(dev);
unsigned int divreg[3], gatereg[2];
int ret;
plat->type = SOCFPGA_A10_CLK_UNKNOWN_CLK;
plat->fix_div = dev_read_u32_default(dev, "fixed-divider", 1);
ret = dev_read_u32_array(dev, "div-reg", divreg, ARRAY_SIZE(divreg));
if (!ret) {
plat->div_reg = divreg[0];
plat->div_len = divreg[2];
plat->div_off = divreg[1];
}
ret = dev_read_u32_array(dev, "clk-gate", gatereg, ARRAY_SIZE(gatereg));
if (!ret) {
plat->gate_reg = gatereg[0];
plat->gate_bit = gatereg[1];
}
return 0;
}
static const struct udevice_id socfpga_a10_clk_match[] = {
{ .compatible = "altr,clk-mgr" },
{}
};
U_BOOT_DRIVER(socfpga_a10_clk) = {
.name = "clk-a10",
.id = UCLASS_CLK,
.of_match = socfpga_a10_clk_match,
.ops = &socfpga_a10_clk_ops,
.bind = socfpga_a10_clk_bind,
.probe = socfpga_a10_clk_probe,
.of_to_plat = socfpga_a10_of_to_plat,
.plat_auto = sizeof(struct socfpga_a10_clk_plat),
};