summary refs log tree commit diff
path: root/drivers/clk/clk-lochnagar.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-05-09 14:50:09 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2019-05-09 14:50:09 -0700
commitea5aee6d97fd2d4499b1eebc233861c1def70f06 (patch)
tree6b332e66bb0ec00b2e35c074ec3f09fb7cf56a1e /drivers/clk/clk-lochnagar.c
parent8e4ff713ce313dcabbb60e6ede1ffc193e67631f (diff)
parentc1157f60d72e8b20efc670cef28883832f42406c (diff)
downloadlinux-ea5aee6d97fd2d4499b1eebc233861c1def70f06.tar.gz
Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
Pull clk framework updates from Stephen Boyd:
 "We have a couple new features and changes in the core clk framework
  this time around because we've finally gotten around to fixing some
  long standing issues. There's still work to do though, so this pull
  request is largely laying down the foundation for all the driver
  changes to come in the next merge window.

  The first problem we're alleviating is how parents of clks are
  specified. With the new method, we should see lots of drivers migrate
  away from the current design of string comparisons on the entire clk
  tree to a more direct method where they can use clk_hw pointers or
  more localized names specified in DT or via clkdev. This should reduce
  our reliance on string comparisons for all the topology description
  logic that we've been using for years and hopefully speed some things
  up while avoiding problems we have with generating clk names.

  Beyond that we also got rid of the CLK_IS_BASIC flag because it wasn't
  really helping anyone and we introduced big-endian versions of the
  basic clk types so that we can get rid of clk_{readl,writel}(). Both
  of these are things that driver developers have tried to use over the
  years that I typically bat away during code reviews because they're
  not useful. It's great to see these two things go away so maintainers
  can save time not worrying about these things.

  On the driver side we got the usual collection of new SoC support and
  non-critical fixes and updates to existing code. The big topics that
  stand out are the new driver support for Mediatek MT8183 and MT8516
  SoCs, Amlogic Meson8b and G12a SoCs, and the SiFive FU540 SoC. The
  other patches in the driver pile are mostly fixes for things that are
  being used for the first time or additions for clks that couldn't be
  tested before because there wasn't a consumer driver that exercised
  them. Details are below and also in the sub-maintainer tags.

  Core:
   - Remove clk_readl() and introduce BE versions of basic clk types
   - Rewrite how clk parents can be specified to allow DT/clkdev lookups
   - Removal of the CLK_IS_BASIC clk flag
   - Framework documentation updates and fixes

  New Drivers:
   - Support for STM32F769
   - AT91 sam9x60 PMC support
   - SiFive FU540 PRCI and PLL support
   - Qualcomm QCS404 CDSP clk support
   - Qualcomm QCS404 Turing clk support
   - Mediatek MT8183 clock support
   - Mediatek MT8516 clock support
   - Milbeaut M10V clk controller support
   - Support for Cirrus Logic Lochnagar clks

  Updates:
   - Rework AT91 sckc DT bindings
   - Fix slow RC oscillator issue on sama5d3
   - Mark UFS clk as critical on Hi-Silicon hi3660 SoCs
   - Various static analysis fixes/finds and const markings
   - Video Engine (ECLK) support on Aspeed SoCs
   - Xilinx ZynqMP Versal platform support
   - Convert Xilinx ZynqMP driver to be struct oriented
   - Fixes for Rockchip rk3328 and rk3288 SoCs
   - Sub-type for Rockchip SoCs where mux and divider aren't a single register
   - Remove SNVS clock from i.MX7UPL clock driver and bindings
   - Improve i.MX5 clock driver for i.MX50 support
   - Addition of ADC clock definition for Exynos 5410 SoC (Odroid XU)
   - Export a new clock for the MBUS controller on the A13
   - Allwinner H6 fixes to support a finer clocking of the video and VPU engines
   - Add g12a support in the Amlogic axg audio clock controller
   - Add missing PCI USB clock on Rensas RZ/N1
   - Add Z2 (Cortex-A53) clocks on Rensas R-Car E3 and RZ/G2E
   - A new helper DIV64_U64_ROUND_CLOSEST() in <linux/math64.h>
   - VPU and Video Decoder clocks on Amlogic Meson8b
   - Finally remove the wrong ABP Meson8b clock id
   - Add Video Decoder, PCIe PLL, and CPU Clocks on Amlogic G12A
   - Re-expose SAR_ADC_SEL and CTS_OSCIN on Amlogic G12A AO clock controller
   - Un-expose some Amlogic AXG-Audio input clocks IDs"

* tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux: (172 commits)
  clk: Cache core in clk_fetch_parent_index() without names
  clk: imx: correct pfdv2 gate_bit/vld_bit operations
  clk: sifive: add a driver for the SiFive FU540 PRCI IP block
  clk: analogbits: add Wide-Range PLL library
  clk: imx: clk-pllv3: mark expected switch fall-throughs
  clk: imx8mq: Add dsi_ipg_div
  clk: imx: pllv4: add fractional-N pll support
  clk: sunxi-ng: Use the correct style for SPDX License Identifier
  clk: sprd: Use the correct style for SPDX License Identifier
  clk: renesas: Use the correct style for SPDX License Identifier
  clk: qcom: Use the correct style for SPDX License Identifier
  clk: davinci: Use the correct style for SPDX License Identifier
  clk: actions: Use the correct style for SPDX License Identifier
  clk: imx: keep uart clock on during system boot
  clk: imx: correct i.MX7D AV PLL num/denom offset
  dt-bindings: clk: add documentation for the SiFive PRCI driver
  clk: stm32mp1: Add ddrperfm clock
  clk: Remove CLK_IS_BASIC clk flag
  clock: milbeaut: Add Milbeaut M10V clock controller
  dt-bindings: clock: milbeaut: add Milbeaut clock description
  ...
Diffstat (limited to 'drivers/clk/clk-lochnagar.c')
-rw-r--r--drivers/clk/clk-lochnagar.c336
1 files changed, 336 insertions, 0 deletions
diff --git a/drivers/clk/clk-lochnagar.c b/drivers/clk/clk-lochnagar.c
new file mode 100644
index 000000000000..a2f31e58ee48
--- /dev/null
+++ b/drivers/clk/clk-lochnagar.c
@@ -0,0 +1,336 @@
+// 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 <ckeepax@opensource.cirrus.com>
+ */
+
+#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/lochnagar.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;
+	enum lochnagar_type type;
+
+	const char **parents;
+	unsigned int nparents;
+
+	struct lochnagar_clk lclks[LOCHNAGAR_NUM_CLOCKS];
+};
+
+static const char * const lochnagar1_clk_parents[] = {
+	"ln-none",
+	"ln-spdif-mclk",
+	"ln-psia1-mclk",
+	"ln-psia2-mclk",
+	"ln-cdc-clkout",
+	"ln-dsp-clkout",
+	"ln-pmic-32k",
+	"ln-gf-mclk1",
+	"ln-gf-mclk3",
+	"ln-gf-mclk2",
+	"ln-gf-mclk4",
+};
+
+static const char * const lochnagar2_clk_parents[] = {
+	"ln-none",
+	"ln-cdc-clkout",
+	"ln-dsp-clkout",
+	"ln-pmic-32k",
+	"ln-spdif-mclk",
+	"ln-clk-12m",
+	"ln-clk-11m",
+	"ln-clk-24m",
+	"ln-clk-22m",
+	"ln-clk-8m",
+	"ln-usb-clk-24m",
+	"ln-gf-mclk1",
+	"ln-gf-mclk3",
+	"ln-gf-mclk2",
+	"ln-psia1-mclk",
+	"ln-psia2-mclk",
+	"ln-spdif-clkout",
+	"ln-adat-mclk",
+	"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"),
+};
+
+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 priv->nparents;
+	}
+
+	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 int lochnagar_init_parents(struct lochnagar_clk_priv *priv)
+{
+	struct device_node *np = priv->dev->of_node;
+	int i, j;
+
+	switch (priv->type) {
+	case LOCHNAGAR1:
+		memcpy(priv->lclks, lochnagar1_clks, sizeof(lochnagar1_clks));
+
+		priv->nparents = ARRAY_SIZE(lochnagar1_clk_parents);
+		priv->parents = devm_kmemdup(priv->dev, lochnagar1_clk_parents,
+					     sizeof(lochnagar1_clk_parents),
+					     GFP_KERNEL);
+		break;
+	case LOCHNAGAR2:
+		memcpy(priv->lclks, lochnagar2_clks, sizeof(lochnagar2_clks));
+
+		priv->nparents = ARRAY_SIZE(lochnagar2_clk_parents);
+		priv->parents = devm_kmemdup(priv->dev, lochnagar2_clk_parents,
+					     sizeof(lochnagar2_clk_parents),
+					     GFP_KERNEL);
+		break;
+	default:
+		dev_err(priv->dev, "Unknown Lochnagar type: %d\n", priv->type);
+		return -EINVAL;
+	}
+
+	if (!priv->parents)
+		return -ENOMEM;
+
+	for (i = 0; i < priv->nparents; i++) {
+		j = of_property_match_string(np, "clock-names",
+					     priv->parents[i]);
+		if (j >= 0)
+			priv->parents[i] = of_clk_get_parent_name(np, j);
+	}
+
+	return 0;
+}
+
+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 int lochnagar_init_clks(struct lochnagar_clk_priv *priv)
+{
+	struct clk_init_data clk_init = {
+		.ops = &lochnagar_clk_ops,
+		.parent_names = priv->parents,
+		.num_parents = priv->nparents,
+	};
+	struct lochnagar_clk *lclk;
+	int ret, i;
+
+	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(priv->dev, &lclk->hw);
+		if (ret) {
+			dev_err(priv->dev, "Failed to register %s: %d\n",
+				lclk->name, ret);
+			return ret;
+		}
+	}
+
+	ret = devm_of_clk_add_hw_provider(priv->dev, lochnagar_of_clk_hw_get,
+					  priv);
+	if (ret < 0)
+		dev_err(priv->dev, "Failed to register provider: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id lochnagar_of_match[] = {
+	{ .compatible = "cirrus,lochnagar1-clk", .data = (void *)LOCHNAGAR1 },
+	{ .compatible = "cirrus,lochnagar2-clk", .data = (void *)LOCHNAGAR2 },
+	{}
+};
+MODULE_DEVICE_TABLE(of, lochnagar_of_match);
+
+static int lochnagar_clk_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct lochnagar_clk_priv *priv;
+	const struct of_device_id *of_id;
+	int ret;
+
+	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);
+	priv->type = (enum lochnagar_type)of_id->data;
+
+	ret = lochnagar_init_parents(priv);
+	if (ret)
+		return ret;
+
+	return lochnagar_init_clks(priv);
+}
+
+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 <ckeepax@opensource.cirrus.com>");
+MODULE_DESCRIPTION("Clock driver for Cirrus Logic Lochnagar Board");
+MODULE_LICENSE("GPL v2");