1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Marvell Armada 37xx SoC Time Base Generator clocks 4 * 5 * Marek Behun <marek.behun@nic.cz> 6 * 7 * Based on Linux driver by: 8 * Gregory CLEMENT <gregory.clement@free-electrons.com> 9 */ 10 11 #include <common.h> 12 #include <clk-uclass.h> 13 #include <clk.h> 14 #include <dm.h> 15 #include <asm/io.h> 16 #include <asm/arch/cpu.h> 17 18 #define NUM_TBG 4 19 20 #define TBG_CTRL0 0x4 21 #define TBG_CTRL1 0x8 22 #define TBG_CTRL7 0x20 23 #define TBG_CTRL8 0x30 24 25 #define TBG_DIV_MASK 0x1FF 26 27 #define TBG_A_REFDIV 0 28 #define TBG_B_REFDIV 16 29 30 #define TBG_A_FBDIV 2 31 #define TBG_B_FBDIV 18 32 33 #define TBG_A_VCODIV_SE 0 34 #define TBG_B_VCODIV_SE 16 35 36 #define TBG_A_VCODIV_DIFF 1 37 #define TBG_B_VCODIV_DIFF 17 38 39 struct tbg_def { 40 const char *name; 41 u32 refdiv_offset; 42 u32 fbdiv_offset; 43 u32 vcodiv_reg; 44 u32 vcodiv_offset; 45 }; 46 47 static const struct tbg_def tbg[NUM_TBG] = { 48 {"TBG-A-P", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL8, TBG_A_VCODIV_DIFF}, 49 {"TBG-B-P", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL8, TBG_B_VCODIV_DIFF}, 50 {"TBG-A-S", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL1, TBG_A_VCODIV_SE}, 51 {"TBG-B-S", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL1, TBG_B_VCODIV_SE}, 52 }; 53 54 struct a37xx_tbgclk { 55 ulong rates[NUM_TBG]; 56 unsigned int mult[NUM_TBG]; 57 unsigned int div[NUM_TBG]; 58 }; 59 60 static unsigned int tbg_get_mult(void __iomem *reg, const struct tbg_def *ptbg) 61 { 62 u32 val; 63 64 val = readl(reg + TBG_CTRL0); 65 66 return ((val >> ptbg->fbdiv_offset) & TBG_DIV_MASK) << 2; 67 } 68 69 static unsigned int tbg_get_div(void __iomem *reg, const struct tbg_def *ptbg) 70 { 71 u32 val; 72 unsigned int div; 73 74 val = readl(reg + TBG_CTRL7); 75 76 div = (val >> ptbg->refdiv_offset) & TBG_DIV_MASK; 77 if (div == 0) 78 div = 1; 79 val = readl(reg + ptbg->vcodiv_reg); 80 81 div *= 1 << ((val >> ptbg->vcodiv_offset) & TBG_DIV_MASK); 82 83 return div; 84 } 85 86 static ulong armada_37xx_tbg_clk_get_rate(struct clk *clk) 87 { 88 struct a37xx_tbgclk *priv = dev_get_priv(clk->dev); 89 90 if (clk->id >= NUM_TBG) 91 return -ENODEV; 92 93 return priv->rates[clk->id]; 94 } 95 96 #if defined(CONFIG_CMD_CLK) && defined(CONFIG_CLK_ARMADA_3720) 97 int armada_37xx_tbg_clk_dump(struct udevice *dev) 98 { 99 struct a37xx_tbgclk *priv = dev_get_priv(dev); 100 int i; 101 102 for (i = 0; i < NUM_TBG; ++i) 103 printf(" %s at %lu Hz\n", tbg[i].name, 104 priv->rates[i]); 105 printf("\n"); 106 107 return 0; 108 } 109 #endif 110 111 static int armada_37xx_tbg_clk_probe(struct udevice *dev) 112 { 113 struct a37xx_tbgclk *priv = dev_get_priv(dev); 114 void __iomem *reg; 115 ulong xtal; 116 int i; 117 118 reg = dev_read_addr_ptr(dev); 119 if (!reg) { 120 dev_err(dev, "no io address\n"); 121 return -ENODEV; 122 } 123 124 xtal = (ulong)get_ref_clk() * 1000000; 125 126 for (i = 0; i < NUM_TBG; ++i) { 127 unsigned int mult, div; 128 129 mult = tbg_get_mult(reg, &tbg[i]); 130 div = tbg_get_div(reg, &tbg[i]); 131 132 priv->rates[i] = (xtal * mult) / div; 133 } 134 135 return 0; 136 } 137 138 static const struct clk_ops armada_37xx_tbg_clk_ops = { 139 .get_rate = armada_37xx_tbg_clk_get_rate, 140 }; 141 142 static const struct udevice_id armada_37xx_tbg_clk_ids[] = { 143 { .compatible = "marvell,armada-3700-tbg-clock" }, 144 {} 145 }; 146 147 U_BOOT_DRIVER(armada_37xx_tbg_clk) = { 148 .name = "armada_37xx_tbg_clk", 149 .id = UCLASS_CLK, 150 .of_match = armada_37xx_tbg_clk_ids, 151 .ops = &armada_37xx_tbg_clk_ops, 152 .priv_auto_alloc_size = sizeof(struct a37xx_tbgclk), 153 .probe = armada_37xx_tbg_clk_probe, 154 }; 155