// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2018 Marek Vasut */ #include #include #include #include #include #include #include #include #include #include #include #include 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), };