mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-06 21:24:29 +00:00
caa4daa2ae
We use 'priv' for private data but often use 'platdata' for platform data. We can't really use 'pdata' since that is ambiguous (it could mean private or platform data). Rename some of the latter variables to end with 'plat' for consistency. Signed-off-by: Simon Glass <sjg@chromium.org>
363 lines
8.8 KiB
C
363 lines
8.8 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/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 &&
|
|
!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_platdata *plat = dev_get_platdata(dev);
|
|
struct socfpga_a10_clk_platdata *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_platdata(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_ofdata_to_platdata(struct udevice *dev)
|
|
{
|
|
struct socfpga_a10_clk_platdata *plat = dev_get_platdata(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,
|
|
.ofdata_to_platdata = socfpga_a10_ofdata_to_platdata,
|
|
|
|
.plat_auto = sizeof(struct socfpga_a10_clk_platdata),
|
|
};
|