1 /* 2 * Marvell Armada 37xx SoC Time Base Generator clocks 3 * 4 * Copyright (C) 2016 Marvell 5 * 6 * Gregory CLEMENT <gregory.clement@free-electrons.com> 7 * 8 * This file is licensed under the terms of the GNU General Public 9 * License version 2 or later. This program is licensed "as is" 10 * without any warranty of any kind, whether express or implied. 11 */ 12 13 #include <linux/clk-provider.h> 14 #include <linux/clk.h> 15 #include <linux/of.h> 16 #include <linux/of_address.h> 17 #include <linux/platform_device.h> 18 #include <linux/slab.h> 19 20 #define NUM_TBG 4 21 22 #define TBG_CTRL0 0x4 23 #define TBG_CTRL1 0x8 24 #define TBG_CTRL7 0x20 25 #define TBG_CTRL8 0x30 26 27 #define TBG_DIV_MASK 0x1FF 28 29 #define TBG_A_REFDIV 0 30 #define TBG_B_REFDIV 16 31 32 #define TBG_A_FBDIV 2 33 #define TBG_B_FBDIV 18 34 35 #define TBG_A_VCODIV_SE 0 36 #define TBG_B_VCODIV_SE 16 37 38 #define TBG_A_VCODIV_DIFF 1 39 #define TBG_B_VCODIV_DIFF 17 40 41 struct tbg_def { 42 char *name; 43 u32 refdiv_offset; 44 u32 fbdiv_offset; 45 u32 vcodiv_reg; 46 u32 vcodiv_offset; 47 }; 48 49 static const struct tbg_def tbg[NUM_TBG] = { 50 {"TBG-A-P", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL8, TBG_A_VCODIV_DIFF}, 51 {"TBG-B-P", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL8, TBG_B_VCODIV_DIFF}, 52 {"TBG-A-S", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL1, TBG_A_VCODIV_SE}, 53 {"TBG-B-S", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL1, TBG_B_VCODIV_SE}, 54 }; 55 56 static unsigned int tbg_get_mult(void __iomem *reg, const struct tbg_def *ptbg) 57 { 58 u32 val; 59 60 val = readl(reg + TBG_CTRL0); 61 62 return ((val >> ptbg->fbdiv_offset) & TBG_DIV_MASK) << 2; 63 } 64 65 static unsigned int tbg_get_div(void __iomem *reg, const struct tbg_def *ptbg) 66 { 67 u32 val; 68 unsigned int div; 69 70 val = readl(reg + TBG_CTRL7); 71 72 div = (val >> ptbg->refdiv_offset) & TBG_DIV_MASK; 73 if (div == 0) 74 div = 1; 75 val = readl(reg + ptbg->vcodiv_reg); 76 77 div *= 1 << ((val >> ptbg->vcodiv_offset) & TBG_DIV_MASK); 78 79 return div; 80 } 81 82 83 static int armada_3700_tbg_clock_probe(struct platform_device *pdev) 84 { 85 struct device_node *np = pdev->dev.of_node; 86 struct clk_hw_onecell_data *hw_tbg_data; 87 struct device *dev = &pdev->dev; 88 const char *parent_name; 89 struct resource *res; 90 struct clk *parent; 91 void __iomem *reg; 92 int i, ret; 93 94 hw_tbg_data = devm_kzalloc(&pdev->dev, sizeof(*hw_tbg_data) 95 + sizeof(*hw_tbg_data->hws) * NUM_TBG, 96 GFP_KERNEL); 97 if (!hw_tbg_data) 98 return -ENOMEM; 99 hw_tbg_data->num = NUM_TBG; 100 platform_set_drvdata(pdev, hw_tbg_data); 101 102 parent = devm_clk_get(dev, NULL); 103 if (IS_ERR(parent)) { 104 dev_err(dev, "Could get the clock parent\n"); 105 return -EINVAL; 106 } 107 parent_name = __clk_get_name(parent); 108 109 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 110 reg = devm_ioremap_resource(dev, res); 111 if (IS_ERR(reg)) 112 return PTR_ERR(reg); 113 114 for (i = 0; i < NUM_TBG; i++) { 115 const char *name; 116 unsigned int mult, div; 117 118 name = tbg[i].name; 119 mult = tbg_get_mult(reg, &tbg[i]); 120 div = tbg_get_div(reg, &tbg[i]); 121 hw_tbg_data->hws[i] = clk_hw_register_fixed_factor(NULL, name, 122 parent_name, 0, mult, div); 123 if (IS_ERR(hw_tbg_data->hws[i])) 124 dev_err(dev, "Can't register TBG clock %s\n", name); 125 } 126 127 ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, hw_tbg_data); 128 129 return ret; 130 } 131 132 static int armada_3700_tbg_clock_remove(struct platform_device *pdev) 133 { 134 int i; 135 struct clk_hw_onecell_data *hw_tbg_data = platform_get_drvdata(pdev); 136 137 of_clk_del_provider(pdev->dev.of_node); 138 for (i = 0; i < hw_tbg_data->num; i++) 139 clk_hw_unregister_fixed_factor(hw_tbg_data->hws[i]); 140 141 return 0; 142 } 143 144 static const struct of_device_id armada_3700_tbg_clock_of_match[] = { 145 { .compatible = "marvell,armada-3700-tbg-clock", }, 146 { } 147 }; 148 149 static struct platform_driver armada_3700_tbg_clock_driver = { 150 .probe = armada_3700_tbg_clock_probe, 151 .remove = armada_3700_tbg_clock_remove, 152 .driver = { 153 .name = "marvell-armada-3700-tbg-clock", 154 .of_match_table = armada_3700_tbg_clock_of_match, 155 }, 156 }; 157 158 builtin_platform_driver(armada_3700_tbg_clock_driver); 159