1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Lochnagar clock control 4 * 5 * Copyright (c) 2017-2018 Cirrus Logic, Inc. and 6 * Cirrus Logic International Semiconductor Ltd. 7 * 8 * Author: Charles Keepax <ckeepax@opensource.cirrus.com> 9 */ 10 11 #include <linux/clk-provider.h> 12 #include <linux/device.h> 13 #include <linux/module.h> 14 #include <linux/of.h> 15 #include <linux/platform_device.h> 16 #include <linux/property.h> 17 #include <linux/regmap.h> 18 19 #include <linux/mfd/lochnagar1_regs.h> 20 #include <linux/mfd/lochnagar2_regs.h> 21 22 #include <dt-bindings/clock/lochnagar.h> 23 24 #define LOCHNAGAR_NUM_CLOCKS (LOCHNAGAR_SPDIF_CLKOUT + 1) 25 26 struct lochnagar_clk { 27 const char * const name; 28 struct clk_hw hw; 29 30 struct lochnagar_clk_priv *priv; 31 32 u16 cfg_reg; 33 u16 ena_mask; 34 35 u16 src_reg; 36 u16 src_mask; 37 }; 38 39 struct lochnagar_clk_priv { 40 struct device *dev; 41 struct regmap *regmap; 42 43 struct lochnagar_clk lclks[LOCHNAGAR_NUM_CLOCKS]; 44 }; 45 46 #define LN_PARENT(NAME) { .name = NAME, .fw_name = NAME } 47 48 static const struct clk_parent_data lochnagar1_clk_parents[] = { 49 LN_PARENT("ln-none"), 50 LN_PARENT("ln-spdif-mclk"), 51 LN_PARENT("ln-psia1-mclk"), 52 LN_PARENT("ln-psia2-mclk"), 53 LN_PARENT("ln-cdc-clkout"), 54 LN_PARENT("ln-dsp-clkout"), 55 LN_PARENT("ln-pmic-32k"), 56 LN_PARENT("ln-gf-mclk1"), 57 LN_PARENT("ln-gf-mclk3"), 58 LN_PARENT("ln-gf-mclk2"), 59 LN_PARENT("ln-gf-mclk4"), 60 }; 61 62 static const struct clk_parent_data lochnagar2_clk_parents[] = { 63 LN_PARENT("ln-none"), 64 LN_PARENT("ln-cdc-clkout"), 65 LN_PARENT("ln-dsp-clkout"), 66 LN_PARENT("ln-pmic-32k"), 67 LN_PARENT("ln-spdif-mclk"), 68 LN_PARENT("ln-clk-12m"), 69 LN_PARENT("ln-clk-11m"), 70 LN_PARENT("ln-clk-24m"), 71 LN_PARENT("ln-clk-22m"), 72 LN_PARENT("ln-clk-8m"), 73 LN_PARENT("ln-usb-clk-24m"), 74 LN_PARENT("ln-gf-mclk1"), 75 LN_PARENT("ln-gf-mclk3"), 76 LN_PARENT("ln-gf-mclk2"), 77 LN_PARENT("ln-psia1-mclk"), 78 LN_PARENT("ln-psia2-mclk"), 79 LN_PARENT("ln-spdif-clkout"), 80 LN_PARENT("ln-adat-mclk"), 81 LN_PARENT("ln-usb-clk-12m"), 82 }; 83 84 #define LN1_CLK(ID, NAME, REG) \ 85 [LOCHNAGAR_##ID] = { \ 86 .name = NAME, \ 87 .cfg_reg = LOCHNAGAR1_##REG, \ 88 .ena_mask = LOCHNAGAR1_##ID##_ENA_MASK, \ 89 .src_reg = LOCHNAGAR1_##ID##_SEL, \ 90 .src_mask = LOCHNAGAR1_SRC_MASK, \ 91 } 92 93 #define LN2_CLK(ID, NAME) \ 94 [LOCHNAGAR_##ID] = { \ 95 .name = NAME, \ 96 .cfg_reg = LOCHNAGAR2_##ID##_CTRL, \ 97 .src_reg = LOCHNAGAR2_##ID##_CTRL, \ 98 .ena_mask = LOCHNAGAR2_CLK_ENA_MASK, \ 99 .src_mask = LOCHNAGAR2_CLK_SRC_MASK, \ 100 } 101 102 static const struct lochnagar_clk lochnagar1_clks[LOCHNAGAR_NUM_CLOCKS] = { 103 LN1_CLK(CDC_MCLK1, "ln-cdc-mclk1", CDC_AIF_CTRL2), 104 LN1_CLK(CDC_MCLK2, "ln-cdc-mclk2", CDC_AIF_CTRL2), 105 LN1_CLK(DSP_CLKIN, "ln-dsp-clkin", DSP_AIF), 106 LN1_CLK(GF_CLKOUT1, "ln-gf-clkout1", GF_AIF1), 107 }; 108 109 static const struct lochnagar_clk lochnagar2_clks[LOCHNAGAR_NUM_CLOCKS] = { 110 LN2_CLK(CDC_MCLK1, "ln-cdc-mclk1"), 111 LN2_CLK(CDC_MCLK2, "ln-cdc-mclk2"), 112 LN2_CLK(DSP_CLKIN, "ln-dsp-clkin"), 113 LN2_CLK(GF_CLKOUT1, "ln-gf-clkout1"), 114 LN2_CLK(GF_CLKOUT2, "ln-gf-clkout2"), 115 LN2_CLK(PSIA1_MCLK, "ln-psia1-mclk"), 116 LN2_CLK(PSIA2_MCLK, "ln-psia2-mclk"), 117 LN2_CLK(SPDIF_MCLK, "ln-spdif-mclk"), 118 LN2_CLK(ADAT_MCLK, "ln-adat-mclk"), 119 LN2_CLK(SOUNDCARD_MCLK, "ln-soundcard-mclk"), 120 }; 121 122 struct lochnagar_config { 123 const struct clk_parent_data *parents; 124 int nparents; 125 const struct lochnagar_clk *clks; 126 }; 127 128 static const struct lochnagar_config lochnagar1_conf = { 129 .parents = lochnagar1_clk_parents, 130 .nparents = ARRAY_SIZE(lochnagar1_clk_parents), 131 .clks = lochnagar1_clks, 132 }; 133 134 static const struct lochnagar_config lochnagar2_conf = { 135 .parents = lochnagar2_clk_parents, 136 .nparents = ARRAY_SIZE(lochnagar2_clk_parents), 137 .clks = lochnagar2_clks, 138 }; 139 140 static inline struct lochnagar_clk *lochnagar_hw_to_lclk(struct clk_hw *hw) 141 { 142 return container_of(hw, struct lochnagar_clk, hw); 143 } 144 145 static int lochnagar_clk_prepare(struct clk_hw *hw) 146 { 147 struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); 148 struct lochnagar_clk_priv *priv = lclk->priv; 149 struct regmap *regmap = priv->regmap; 150 int ret; 151 152 ret = regmap_update_bits(regmap, lclk->cfg_reg, 153 lclk->ena_mask, lclk->ena_mask); 154 if (ret < 0) 155 dev_dbg(priv->dev, "Failed to prepare %s: %d\n", 156 lclk->name, ret); 157 158 return ret; 159 } 160 161 static void lochnagar_clk_unprepare(struct clk_hw *hw) 162 { 163 struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); 164 struct lochnagar_clk_priv *priv = lclk->priv; 165 struct regmap *regmap = priv->regmap; 166 int ret; 167 168 ret = regmap_update_bits(regmap, lclk->cfg_reg, lclk->ena_mask, 0); 169 if (ret < 0) 170 dev_dbg(priv->dev, "Failed to unprepare %s: %d\n", 171 lclk->name, ret); 172 } 173 174 static int lochnagar_clk_set_parent(struct clk_hw *hw, u8 index) 175 { 176 struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); 177 struct lochnagar_clk_priv *priv = lclk->priv; 178 struct regmap *regmap = priv->regmap; 179 int ret; 180 181 ret = regmap_update_bits(regmap, lclk->src_reg, lclk->src_mask, index); 182 if (ret < 0) 183 dev_dbg(priv->dev, "Failed to reparent %s: %d\n", 184 lclk->name, ret); 185 186 return ret; 187 } 188 189 static u8 lochnagar_clk_get_parent(struct clk_hw *hw) 190 { 191 struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); 192 struct lochnagar_clk_priv *priv = lclk->priv; 193 struct regmap *regmap = priv->regmap; 194 unsigned int val; 195 int ret; 196 197 ret = regmap_read(regmap, lclk->src_reg, &val); 198 if (ret < 0) { 199 dev_dbg(priv->dev, "Failed to read parent of %s: %d\n", 200 lclk->name, ret); 201 return clk_hw_get_num_parents(hw); 202 } 203 204 val &= lclk->src_mask; 205 206 return val; 207 } 208 209 static const struct clk_ops lochnagar_clk_ops = { 210 .prepare = lochnagar_clk_prepare, 211 .unprepare = lochnagar_clk_unprepare, 212 .determine_rate = clk_hw_determine_rate_no_reparent, 213 .set_parent = lochnagar_clk_set_parent, 214 .get_parent = lochnagar_clk_get_parent, 215 }; 216 217 static struct clk_hw * 218 lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data) 219 { 220 struct lochnagar_clk_priv *priv = data; 221 unsigned int idx = clkspec->args[0]; 222 223 if (idx >= ARRAY_SIZE(priv->lclks)) { 224 dev_err(priv->dev, "Invalid index %u\n", idx); 225 return ERR_PTR(-EINVAL); 226 } 227 228 return &priv->lclks[idx].hw; 229 } 230 231 static const struct of_device_id lochnagar_of_match[] = { 232 { .compatible = "cirrus,lochnagar1-clk", .data = &lochnagar1_conf }, 233 { .compatible = "cirrus,lochnagar2-clk", .data = &lochnagar2_conf }, 234 {} 235 }; 236 MODULE_DEVICE_TABLE(of, lochnagar_of_match); 237 238 static int lochnagar_clk_probe(struct platform_device *pdev) 239 { 240 struct clk_init_data clk_init = { 241 .ops = &lochnagar_clk_ops, 242 }; 243 struct device *dev = &pdev->dev; 244 struct lochnagar_clk_priv *priv; 245 struct lochnagar_clk *lclk; 246 struct lochnagar_config *conf; 247 int ret, i; 248 249 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 250 if (!priv) 251 return -ENOMEM; 252 253 priv->dev = dev; 254 priv->regmap = dev_get_regmap(dev->parent, NULL); 255 conf = (struct lochnagar_config *)device_get_match_data(dev); 256 257 memcpy(priv->lclks, conf->clks, sizeof(priv->lclks)); 258 259 clk_init.parent_data = conf->parents; 260 clk_init.num_parents = conf->nparents; 261 262 for (i = 0; i < ARRAY_SIZE(priv->lclks); i++) { 263 lclk = &priv->lclks[i]; 264 265 if (!lclk->name) 266 continue; 267 268 clk_init.name = lclk->name; 269 270 lclk->priv = priv; 271 lclk->hw.init = &clk_init; 272 273 ret = devm_clk_hw_register(dev, &lclk->hw); 274 if (ret) { 275 dev_err(dev, "Failed to register %s: %d\n", 276 lclk->name, ret); 277 return ret; 278 } 279 } 280 281 ret = devm_of_clk_add_hw_provider(dev, lochnagar_of_clk_hw_get, priv); 282 if (ret < 0) 283 dev_err(dev, "Failed to register provider: %d\n", ret); 284 285 return ret; 286 } 287 288 static struct platform_driver lochnagar_clk_driver = { 289 .driver = { 290 .name = "lochnagar-clk", 291 .of_match_table = lochnagar_of_match, 292 }, 293 .probe = lochnagar_clk_probe, 294 }; 295 module_platform_driver(lochnagar_clk_driver); 296 297 MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>"); 298 MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board"); 299 MODULE_LICENSE("GPL v2"); 300