linux/drivers/clk/clk-lochnagar.c
<<
>>
Prefs
   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");
 304