forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
7.7 KiB
303 lines
7.7 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Lochnagar clock control |
|
* |
|
* Copyright (c) 2017-2018 Cirrus Logic, Inc. and |
|
* Cirrus Logic International Semiconductor Ltd. |
|
* |
|
* Author: Charles Keepax <[email protected]> |
|
*/ |
|
|
|
#include <linux/clk-provider.h> |
|
#include <linux/device.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/of_device.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/regmap.h> |
|
|
|
#include <linux/mfd/lochnagar1_regs.h> |
|
#include <linux/mfd/lochnagar2_regs.h> |
|
|
|
#include <dt-bindings/clk/lochnagar.h> |
|
|
|
#define LOCHNAGAR_NUM_CLOCKS (LOCHNAGAR_SPDIF_CLKOUT + 1) |
|
|
|
struct lochnagar_clk { |
|
const char * const name; |
|
struct clk_hw hw; |
|
|
|
struct lochnagar_clk_priv *priv; |
|
|
|
u16 cfg_reg; |
|
u16 ena_mask; |
|
|
|
u16 src_reg; |
|
u16 src_mask; |
|
}; |
|
|
|
struct lochnagar_clk_priv { |
|
struct device *dev; |
|
struct regmap *regmap; |
|
|
|
struct lochnagar_clk lclks[LOCHNAGAR_NUM_CLOCKS]; |
|
}; |
|
|
|
#define LN_PARENT(NAME) { .name = NAME, .fw_name = NAME } |
|
|
|
static const struct clk_parent_data lochnagar1_clk_parents[] = { |
|
LN_PARENT("ln-none"), |
|
LN_PARENT("ln-spdif-mclk"), |
|
LN_PARENT("ln-psia1-mclk"), |
|
LN_PARENT("ln-psia2-mclk"), |
|
LN_PARENT("ln-cdc-clkout"), |
|
LN_PARENT("ln-dsp-clkout"), |
|
LN_PARENT("ln-pmic-32k"), |
|
LN_PARENT("ln-gf-mclk1"), |
|
LN_PARENT("ln-gf-mclk3"), |
|
LN_PARENT("ln-gf-mclk2"), |
|
LN_PARENT("ln-gf-mclk4"), |
|
}; |
|
|
|
static const struct clk_parent_data lochnagar2_clk_parents[] = { |
|
LN_PARENT("ln-none"), |
|
LN_PARENT("ln-cdc-clkout"), |
|
LN_PARENT("ln-dsp-clkout"), |
|
LN_PARENT("ln-pmic-32k"), |
|
LN_PARENT("ln-spdif-mclk"), |
|
LN_PARENT("ln-clk-12m"), |
|
LN_PARENT("ln-clk-11m"), |
|
LN_PARENT("ln-clk-24m"), |
|
LN_PARENT("ln-clk-22m"), |
|
LN_PARENT("ln-clk-8m"), |
|
LN_PARENT("ln-usb-clk-24m"), |
|
LN_PARENT("ln-gf-mclk1"), |
|
LN_PARENT("ln-gf-mclk3"), |
|
LN_PARENT("ln-gf-mclk2"), |
|
LN_PARENT("ln-psia1-mclk"), |
|
LN_PARENT("ln-psia2-mclk"), |
|
LN_PARENT("ln-spdif-clkout"), |
|
LN_PARENT("ln-adat-mclk"), |
|
LN_PARENT("ln-usb-clk-12m"), |
|
}; |
|
|
|
#define LN1_CLK(ID, NAME, REG) \ |
|
[LOCHNAGAR_##ID] = { \ |
|
.name = NAME, \ |
|
.cfg_reg = LOCHNAGAR1_##REG, \ |
|
.ena_mask = LOCHNAGAR1_##ID##_ENA_MASK, \ |
|
.src_reg = LOCHNAGAR1_##ID##_SEL, \ |
|
.src_mask = LOCHNAGAR1_SRC_MASK, \ |
|
} |
|
|
|
#define LN2_CLK(ID, NAME) \ |
|
[LOCHNAGAR_##ID] = { \ |
|
.name = NAME, \ |
|
.cfg_reg = LOCHNAGAR2_##ID##_CTRL, \ |
|
.src_reg = LOCHNAGAR2_##ID##_CTRL, \ |
|
.ena_mask = LOCHNAGAR2_CLK_ENA_MASK, \ |
|
.src_mask = LOCHNAGAR2_CLK_SRC_MASK, \ |
|
} |
|
|
|
static const struct lochnagar_clk lochnagar1_clks[LOCHNAGAR_NUM_CLOCKS] = { |
|
LN1_CLK(CDC_MCLK1, "ln-cdc-mclk1", CDC_AIF_CTRL2), |
|
LN1_CLK(CDC_MCLK2, "ln-cdc-mclk2", CDC_AIF_CTRL2), |
|
LN1_CLK(DSP_CLKIN, "ln-dsp-clkin", DSP_AIF), |
|
LN1_CLK(GF_CLKOUT1, "ln-gf-clkout1", GF_AIF1), |
|
}; |
|
|
|
static const struct lochnagar_clk lochnagar2_clks[LOCHNAGAR_NUM_CLOCKS] = { |
|
LN2_CLK(CDC_MCLK1, "ln-cdc-mclk1"), |
|
LN2_CLK(CDC_MCLK2, "ln-cdc-mclk2"), |
|
LN2_CLK(DSP_CLKIN, "ln-dsp-clkin"), |
|
LN2_CLK(GF_CLKOUT1, "ln-gf-clkout1"), |
|
LN2_CLK(GF_CLKOUT2, "ln-gf-clkout2"), |
|
LN2_CLK(PSIA1_MCLK, "ln-psia1-mclk"), |
|
LN2_CLK(PSIA2_MCLK, "ln-psia2-mclk"), |
|
LN2_CLK(SPDIF_MCLK, "ln-spdif-mclk"), |
|
LN2_CLK(ADAT_MCLK, "ln-adat-mclk"), |
|
LN2_CLK(SOUNDCARD_MCLK, "ln-soundcard-mclk"), |
|
}; |
|
|
|
struct lochnagar_config { |
|
const struct clk_parent_data *parents; |
|
int nparents; |
|
const struct lochnagar_clk *clks; |
|
}; |
|
|
|
static const struct lochnagar_config lochnagar1_conf = { |
|
.parents = lochnagar1_clk_parents, |
|
.nparents = ARRAY_SIZE(lochnagar1_clk_parents), |
|
.clks = lochnagar1_clks, |
|
}; |
|
|
|
static const struct lochnagar_config lochnagar2_conf = { |
|
.parents = lochnagar2_clk_parents, |
|
.nparents = ARRAY_SIZE(lochnagar2_clk_parents), |
|
.clks = lochnagar2_clks, |
|
}; |
|
|
|
static inline struct lochnagar_clk *lochnagar_hw_to_lclk(struct clk_hw *hw) |
|
{ |
|
return container_of(hw, struct lochnagar_clk, hw); |
|
} |
|
|
|
static int lochnagar_clk_prepare(struct clk_hw *hw) |
|
{ |
|
struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); |
|
struct lochnagar_clk_priv *priv = lclk->priv; |
|
struct regmap *regmap = priv->regmap; |
|
int ret; |
|
|
|
ret = regmap_update_bits(regmap, lclk->cfg_reg, |
|
lclk->ena_mask, lclk->ena_mask); |
|
if (ret < 0) |
|
dev_dbg(priv->dev, "Failed to prepare %s: %d\n", |
|
lclk->name, ret); |
|
|
|
return ret; |
|
} |
|
|
|
static void lochnagar_clk_unprepare(struct clk_hw *hw) |
|
{ |
|
struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); |
|
struct lochnagar_clk_priv *priv = lclk->priv; |
|
struct regmap *regmap = priv->regmap; |
|
int ret; |
|
|
|
ret = regmap_update_bits(regmap, lclk->cfg_reg, lclk->ena_mask, 0); |
|
if (ret < 0) |
|
dev_dbg(priv->dev, "Failed to unprepare %s: %d\n", |
|
lclk->name, ret); |
|
} |
|
|
|
static int lochnagar_clk_set_parent(struct clk_hw *hw, u8 index) |
|
{ |
|
struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); |
|
struct lochnagar_clk_priv *priv = lclk->priv; |
|
struct regmap *regmap = priv->regmap; |
|
int ret; |
|
|
|
ret = regmap_update_bits(regmap, lclk->src_reg, lclk->src_mask, index); |
|
if (ret < 0) |
|
dev_dbg(priv->dev, "Failed to reparent %s: %d\n", |
|
lclk->name, ret); |
|
|
|
return ret; |
|
} |
|
|
|
static u8 lochnagar_clk_get_parent(struct clk_hw *hw) |
|
{ |
|
struct lochnagar_clk *lclk = lochnagar_hw_to_lclk(hw); |
|
struct lochnagar_clk_priv *priv = lclk->priv; |
|
struct regmap *regmap = priv->regmap; |
|
unsigned int val; |
|
int ret; |
|
|
|
ret = regmap_read(regmap, lclk->src_reg, &val); |
|
if (ret < 0) { |
|
dev_dbg(priv->dev, "Failed to read parent of %s: %d\n", |
|
lclk->name, ret); |
|
return clk_hw_get_num_parents(hw); |
|
} |
|
|
|
val &= lclk->src_mask; |
|
|
|
return val; |
|
} |
|
|
|
static const struct clk_ops lochnagar_clk_ops = { |
|
.prepare = lochnagar_clk_prepare, |
|
.unprepare = lochnagar_clk_unprepare, |
|
.set_parent = lochnagar_clk_set_parent, |
|
.get_parent = lochnagar_clk_get_parent, |
|
}; |
|
|
|
static struct clk_hw * |
|
lochnagar_of_clk_hw_get(struct of_phandle_args *clkspec, void *data) |
|
{ |
|
struct lochnagar_clk_priv *priv = data; |
|
unsigned int idx = clkspec->args[0]; |
|
|
|
if (idx >= ARRAY_SIZE(priv->lclks)) { |
|
dev_err(priv->dev, "Invalid index %u\n", idx); |
|
return ERR_PTR(-EINVAL); |
|
} |
|
|
|
return &priv->lclks[idx].hw; |
|
} |
|
|
|
static const struct of_device_id lochnagar_of_match[] = { |
|
{ .compatible = "cirrus,lochnagar1-clk", .data = &lochnagar1_conf }, |
|
{ .compatible = "cirrus,lochnagar2-clk", .data = &lochnagar2_conf }, |
|
{} |
|
}; |
|
MODULE_DEVICE_TABLE(of, lochnagar_of_match); |
|
|
|
static int lochnagar_clk_probe(struct platform_device *pdev) |
|
{ |
|
struct clk_init_data clk_init = { |
|
.ops = &lochnagar_clk_ops, |
|
}; |
|
struct device *dev = &pdev->dev; |
|
struct lochnagar_clk_priv *priv; |
|
const struct of_device_id *of_id; |
|
struct lochnagar_clk *lclk; |
|
struct lochnagar_config *conf; |
|
int ret, i; |
|
|
|
of_id = of_match_device(lochnagar_of_match, dev); |
|
if (!of_id) |
|
return -EINVAL; |
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
priv->dev = dev; |
|
priv->regmap = dev_get_regmap(dev->parent, NULL); |
|
conf = (struct lochnagar_config *)of_id->data; |
|
|
|
memcpy(priv->lclks, conf->clks, sizeof(priv->lclks)); |
|
|
|
clk_init.parent_data = conf->parents; |
|
clk_init.num_parents = conf->nparents; |
|
|
|
for (i = 0; i < ARRAY_SIZE(priv->lclks); i++) { |
|
lclk = &priv->lclks[i]; |
|
|
|
if (!lclk->name) |
|
continue; |
|
|
|
clk_init.name = lclk->name; |
|
|
|
lclk->priv = priv; |
|
lclk->hw.init = &clk_init; |
|
|
|
ret = devm_clk_hw_register(dev, &lclk->hw); |
|
if (ret) { |
|
dev_err(dev, "Failed to register %s: %d\n", |
|
lclk->name, ret); |
|
return ret; |
|
} |
|
} |
|
|
|
ret = devm_of_clk_add_hw_provider(dev, lochnagar_of_clk_hw_get, priv); |
|
if (ret < 0) |
|
dev_err(dev, "Failed to register provider: %d\n", ret); |
|
|
|
return ret; |
|
} |
|
|
|
static struct platform_driver lochnagar_clk_driver = { |
|
.driver = { |
|
.name = "lochnagar-clk", |
|
.of_match_table = lochnagar_of_match, |
|
}, |
|
.probe = lochnagar_clk_probe, |
|
}; |
|
module_platform_driver(lochnagar_clk_driver); |
|
|
|
MODULE_AUTHOR("Charles Keepax <[email protected]>"); |
|
MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board"); |
|
MODULE_LICENSE("GPL v2");
|
|
|