clk: socfpga: Add initial Arria10 clock driver

Add clock driver for the Arria10, which allows reading the clock
frequency from all the clock described in the DT. The driver also
allows enabling and disabling the clock. Reconfiguring frequency
is not supported thus far.

Since the DT bindings for the SoCFPGA clock are massively misdesigned
and the handoff DT adds additional incorrectly described entries to
the DT, the driver contains workarounds which attempt to rectify all
of those problems.

Signed-off-by: Marek Vasut <marex@denx.de>
Cc: Chin Liang See <chin.liang.see@intel.com>
Cc: Dinh Nguyen <dinguyen@kernel.org>
Cc: Ley Foon Tan <ley.foon.tan@intel.com>
This commit is contained in:
Marek Vasut 2018-07-31 17:58:07 +02:00
parent ccc97432ad
commit f9f016adcd
3 changed files with 371 additions and 0 deletions

View file

@ -11,6 +11,7 @@ obj-y += tegra/
obj-$(CONFIG_ARCH_ASPEED) += aspeed/
obj-$(CONFIG_ARCH_MESON) += clk_meson.o
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
obj-$(CONFIG_ARCH_SOCFPGA) += altera/
obj-$(CONFIG_CLK_AT91) += at91/
obj-$(CONFIG_CLK_MVEBU) += mvebu/
obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o

View file

@ -0,0 +1,7 @@
#
# Copyright (C) 2018 Marek Vasut <marex@denx.de>
#
# SPDX-License-Identifier: GPL-2.0+
#
obj-$(CONFIG_TARGET_SOCFPGA_ARRIA10) += clk-arria10.o

View file

@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 Marek Vasut <marex@denx.de>
*/
#include <common.h>
#include <asm/io.h>
#include <clk-uclass.h>
#include <dm.h>
#include <dm/lists.h>
#include <dm/util.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_platdata {
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_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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 && !dm_fdt_pre_reloc(fdt, 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_platdata *plat = dev_get_platdata(dev);
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,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_ofdata_to_platdata(struct udevice *dev)
{
struct socfpga_a10_clk_platdata *plat = dev_get_platdata(dev);
struct socfpga_a10_clk_platdata *pplat;
struct udevice *pdev;
const void *fdt = gd->fdt_blob;
unsigned int divreg[3], gatereg[2];
int ret, offset = dev_of_offset(dev);
u32 regs;
regs = dev_read_u32_default(dev, "reg", 0x0);
if (!fdt_node_check_compatible(fdt, offset, "altr,clk-mgr")) {
plat->regs = devfdt_get_addr(dev);
} else {
pdev = dev_get_parent(dev);
if (!pdev)
return -ENODEV;
pplat = dev_get_platdata(pdev);
if (!pplat)
return -EINVAL;
plat->ctl_reg = regs;
plat->regs = pplat->regs;
}
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,
.flags = DM_FLAG_PRE_RELOC,
.of_match = socfpga_a10_clk_match,
.ops = &socfpga_a10_clk_ops,
.bind = socfpga_a10_clk_bind,
.probe = socfpga_a10_clk_probe,
.ofdata_to_platdata = socfpga_a10_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct socfpga_a10_clk_platdata),
};