mirror of
https://github.com/smaeul/u-boot.git
synced 2025-11-01 12:38:22 +00:00
When bringing in the series 'arm: dts: am62-beagleplay: Fix Beagleplay Ethernet"' I failed to notice that b4 noticed it was based on next and so took that as the base commit and merged that part of next to master. This reverts commit c8ffd1356d42223cbb8c86280a083cc3c93e6426, reversing changes made to 2ee6f3a5f7550de3599faef9704e166e5dcace35. Reported-by: Jonas Karlman <jonas@kwiboo.se> Signed-off-by: Tom Rini <trini@konsulko.com>
365 lines
8.7 KiB
C
365 lines
8.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2018 Marek Vasut <marex@denx.de>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#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),
|
|
};
|