300 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Lochnagar clock control4 *5 * Copyright (c) 2017-2018 Cirrus Logic, Inc. and6 * 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 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 .determine_rate = clk_hw_determine_rate_no_reparent,213 .set_parent = lochnagar_clk_set_parent,214 .get_parent = lochnagar_clk_get_parent,215};216 217static struct clk_hw *218lochnagar_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 231static 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};236MODULE_DEVICE_TABLE(of, lochnagar_of_match);237 238static 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 288static 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};295module_platform_driver(lochnagar_clk_driver);296 297MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>");298MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board");299MODULE_LICENSE("GPL v2");300