clk-lochnagar.c (7844B)
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/of_device.h> 16#include <linux/platform_device.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/clk/lochnagar.h> 23 24#define LOCHNAGAR_NUM_CLOCKS (LOCHNAGAR_SPDIF_CLKOUT + 1) 25 26struct 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 39struct 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 48static 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 62static 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 102static 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 109static 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 122struct lochnagar_config { 123 const struct clk_parent_data *parents; 124 int nparents; 125 const struct lochnagar_clk *clks; 126}; 127 128static const struct lochnagar_config lochnagar1_conf = { 129 .parents = lochnagar1_clk_parents, 130 .nparents = ARRAY_SIZE(lochnagar1_clk_parents), 131 .clks = lochnagar1_clks, 132}; 133 134static const struct lochnagar_config lochnagar2_conf = { 135 .parents = lochnagar2_clk_parents, 136 .nparents = ARRAY_SIZE(lochnagar2_clk_parents), 137 .clks = lochnagar2_clks, 138}; 139 140static inline struct lochnagar_clk *lochnagar_hw_to_lclk(struct clk_hw *hw) 141{ 142 return container_of(hw, struct lochnagar_clk, hw); 143} 144 145static 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 161static 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 174static 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 189static 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 209static const struct clk_ops lochnagar_clk_ops = { 210 .prepare = lochnagar_clk_prepare, 211 .unprepare = lochnagar_clk_unprepare, 212 .set_parent = lochnagar_clk_set_parent, 213 .get_parent = lochnagar_clk_get_parent, 214}; 215 216static struct clk_hw * 217lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data) 218{ 219 struct lochnagar_clk_priv *priv = data; 220 unsigned int idx = clkspec->args[0]; 221 222 if (idx >= ARRAY_SIZE(priv->lclks)) { 223 dev_err(priv->dev, "Invalid index %u\n", idx); 224 return ERR_PTR(-EINVAL); 225 } 226 227 return &priv->lclks[idx].hw; 228} 229 230static const struct of_device_id lochnagar_of_match[] = { 231 { .compatible = "cirrus,lochnagar1-clk", .data = &lochnagar1_conf }, 232 { .compatible = "cirrus,lochnagar2-clk", .data = &lochnagar2_conf }, 233 {} 234}; 235MODULE_DEVICE_TABLE(of, lochnagar_of_match); 236 237static int lochnagar_clk_probe(struct platform_device *pdev) 238{ 239 struct clk_init_data clk_init = { 240 .ops = &lochnagar_clk_ops, 241 }; 242 struct device *dev = &pdev->dev; 243 struct lochnagar_clk_priv *priv; 244 const struct of_device_id *of_id; 245 struct lochnagar_clk *lclk; 246 struct lochnagar_config *conf; 247 int ret, i; 248 249 of_id = of_match_device(lochnagar_of_match, dev); 250 if (!of_id) 251 return -EINVAL; 252 253 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 254 if (!priv) 255 return -ENOMEM; 256 257 priv->dev = dev; 258 priv->regmap = dev_get_regmap(dev->parent, NULL); 259 conf = (struct lochnagar_config *)of_id->data; 260 261 memcpy(priv->lclks, conf->clks, sizeof(priv->lclks)); 262 263 clk_init.parent_data = conf->parents; 264 clk_init.num_parents = conf->nparents; 265 266 for (i = 0; i < ARRAY_SIZE(priv->lclks); i++) { 267 lclk = &priv->lclks[i]; 268 269 if (!lclk->name) 270 continue; 271 272 clk_init.name = lclk->name; 273 274 lclk->priv = priv; 275 lclk->hw.init = &clk_init; 276 277 ret = devm_clk_hw_register(dev, &lclk->hw); 278 if (ret) { 279 dev_err(dev, "Failed to register %s: %d\n", 280 lclk->name, ret); 281 return ret; 282 } 283 } 284 285 ret = devm_of_clk_add_hw_provider(dev, lochnagar_of_clk_hw_get, priv); 286 if (ret < 0) 287 dev_err(dev, "Failed to register provider: %d\n", ret); 288 289 return ret; 290} 291 292static struct platform_driver lochnagar_clk_driver = { 293 .driver = { 294 .name = "lochnagar-clk", 295 .of_match_table = lochnagar_of_match, 296 }, 297 .probe = lochnagar_clk_probe, 298}; 299module_platform_driver(lochnagar_clk_driver); 300 301MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>"); 302MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board"); 303MODULE_LICENSE("GPL v2");