[09/10] clk: Add support for AD242x clock output providers
diff mbox series

Message ID 20191209183511.3576038-11-daniel@zonque.org
State New
Headers show
Series
  • mfd: Add support for Analog Devices A2B transceiver
Related show

Commit Message

Daniel Mack Dec. 9, 2019, 6:35 p.m. UTC
AD242x have two pins that can be used as clock outputs. This driver makes
that functionality available through the common clock framework.

Apart from gating the clocks and setting their rates, the hardware also
allows for a phase shift of 180 degrees.

Signed-off-by: Daniel Mack <daniel@zonque.org>
---
 drivers/clk/Kconfig      |   6 +
 drivers/clk/Makefile     |   1 +
 drivers/clk/clk-ad242x.c | 231 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 238 insertions(+)
 create mode 100644 drivers/clk/clk-ad242x.c

Comments

Stephen Boyd Dec. 24, 2019, 7:46 a.m. UTC | #1
Quoting Daniel Mack (2019-12-09 10:35:10)
> diff --git a/drivers/clk/clk-ad242x.c b/drivers/clk/clk-ad242x.c
> new file mode 100644
> index 000000000000..201789d8f174
> --- /dev/null
> +++ b/drivers/clk/clk-ad242x.c
> @@ -0,0 +1,231 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/clk.h>

Is this include used?

> +#include <linux/clk-provider.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>

Is this include used?

> +#include <linux/mfd/ad242x.h>

Any way we can avoid this build dependency? Maybe just put defines in
this driver that deals with the clk bits of the device?

> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#include <dt-bindings/clock/adi,ad242x.h>
> +
> +#define AD242X_NUM_CLKS 2
> +
> +struct ad242x_clk_hw {
> +       struct clk_hw hw;
> +       struct clk_init_data init;

Do we need to keep around this init data after probe? I'd rather leave
this out.

> +       struct ad242x_node *node;

What's the point of this structure? Can we use dev->parent->regmap and
just store the struct regmap pointer here instead of using this custom
struct?

> +       u8 reg;
> +};
> +
> +struct ad242x_clk_driver_data {
> +       struct ad242x_clk_hw hw[AD242X_NUM_CLKS];

If this is the only drvdata, then I'd prefer just the array and not
another struct so we can have clarity.

> +};
> +
> +static inline struct ad242x_clk_hw *to_ad242x_clk(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct ad242x_clk_hw, hw);
> +}
> +
[...]
> +
> +static long ad242x_clk_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                 unsigned long *parent_rate)
> +{
> +       unsigned long pll_rate = *parent_rate * 2048UL;
> +       unsigned long prediv, div;
> +
> +       if (rate > pll_rate / 4 || rate < pll_rate / 1024UL)
> +               return -EINVAL;

This callback should round the rate to something valid. If the rate is
larger than pll_rate / 4 then it should clamp to be the highest rate
supported. Likewise for something slow.

> +
> +       ad242x_do_div(rate, pll_rate, &prediv, &div);
> +
> +       return pll_rate / (prediv * div);
> +}
> +
[...]
> +
> +static struct clk_hw *
> +ad242x_of_clk_get(struct of_phandle_args *clkspec, void *data)
> +{
> +       struct ad242x_clk_driver_data *drvdata = data;
> +       unsigned int idx = clkspec->args[0];
> +
> +       return &drvdata->hw[idx].hw;

It looks quite a bit like of_clk_hw_onecell_get(). Can that be used? Or
at least check for out of bounds and return failure?

> +}
> +
> +static int ad242x_clk_probe(struct platform_device *pdev)
> +{
> +       const char *clk_names[AD242X_NUM_CLKS] = { "clkout1", "clkout2" };
> +       u8 regs[AD242X_NUM_CLKS] = { AD242X_CLK1CFG, AD242X_CLK2CFG };
> +       struct ad242x_clk_driver_data *drvdata;
> +       struct device *dev = &pdev->dev;
> +       const char *sync_clk_name;
> +       struct ad242x_node *node;
> +       int i, ret;
> +
> +       if (!dev->of_node)
> +               return -ENODEV;

Please drop this. It's not useful.

> +
> +       drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
> +       if (!drvdata)
> +               return -ENOMEM;
> +
> +       node = dev_get_drvdata(dev->parent);

Add a NULL check on node?

> +       sync_clk_name = ad242x_master_get_clk_name(node->master);
> +
> +       for (i = 0; i < AD242X_NUM_CLKS; i++) {
> +               const char *name;
> +
> +               if (of_property_read_string_index(dev->of_node,
> +                                                 "clock-output-names",
> +                                                 i, &name) == 0)
> +                       drvdata->hw[i].init.name = name;
> +               else
> +                       drvdata->hw[i].init.name = clk_names[i];

Do you need unique names? Or can you generate psuedo unique names based
on the device name and clk number?

> +
> +               drvdata->hw[i].reg = regs[i];
> +               drvdata->hw[i].init.ops = &ad242x_clk_ops;
> +               drvdata->hw[i].init.num_parents = 1;
> +               drvdata->hw[i].init.parent_names = &sync_clk_name;
> +               drvdata->hw[i].hw.init = &drvdata->hw[i].init;
> +               drvdata->hw[i].node = node;
> +
> +               ret = devm_clk_hw_register(dev, &drvdata->hw[i].hw);
> +               if (ret < 0)
> +                       return ret;
> +       }
> +
> +       return devm_of_clk_add_hw_provider(dev, ad242x_of_clk_get, drvdata);
> +}

Patch
diff mbox series

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 45653a0e6ecd..28b700d068fe 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -39,6 +39,12 @@  config CLK_HSDK
 	  This driver supports the HSDK core, system, ddr, tunnel and hdmi PLLs
 	  control.
 
+config COMMON_CLK_AD242X
+	tristate "Clock driver for AD242x A2B nodes"
+	depends on MFD_AD242X
+	---help---
+	  This driver supports clock outputs on AD242x A2B nodes.
+
 config COMMON_CLK_MAX77686
 	tristate "Clock driver for Maxim 77620/77686/77802 MFD"
 	depends on MFD_MAX77686 || MFD_MAX77620 || COMPILE_TEST
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 0696a0c1ab58..3f8cbddb48c7 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -18,6 +18,7 @@  endif
 
 # hardware specific clock types
 # please keep this section sorted lexicographically by file path name
+obj-$(CONFIG_COMMON_CLK_AD242X)		+= clk-ad242x.o
 obj-$(CONFIG_MACH_ASM9260)		+= clk-asm9260.o
 obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN)	+= clk-axi-clkgen.o
 obj-$(CONFIG_ARCH_AXXIA)		+= clk-axm5516.o
diff --git a/drivers/clk/clk-ad242x.c b/drivers/clk/clk-ad242x.c
new file mode 100644
index 000000000000..201789d8f174
--- /dev/null
+++ b/drivers/clk/clk-ad242x.c
@@ -0,0 +1,231 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/mfd/ad242x.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/adi,ad242x.h>
+
+#define AD242X_NUM_CLKS 2
+
+struct ad242x_clk_hw {
+	struct clk_hw hw;
+	struct clk_init_data init;
+	struct ad242x_node *node;
+	u8 reg;
+};
+
+struct ad242x_clk_driver_data {
+	struct ad242x_clk_hw hw[AD242X_NUM_CLKS];
+};
+
+static inline struct ad242x_clk_hw *to_ad242x_clk(struct clk_hw *hw)
+{
+	return container_of(hw, struct ad242x_clk_hw, hw);
+}
+
+static int ad242x_clk_prepare(struct clk_hw *hw)
+{
+	struct ad242x_clk_hw *clk_hw = to_ad242x_clk(hw);
+
+	return regmap_update_bits(clk_hw->node->regmap, clk_hw->reg,
+				  AD242X_CLKCFG_EN, AD242X_CLKCFG_EN);
+}
+
+static void ad242x_clk_unprepare(struct clk_hw *hw)
+{
+	struct ad242x_clk_hw *clk_hw = to_ad242x_clk(hw);
+
+	regmap_update_bits(clk_hw->node->regmap, clk_hw->reg,
+			   AD242X_CLKCFG_EN, 0);
+}
+
+static void ad242x_do_div(unsigned long rate, unsigned long parent_rate,
+			  unsigned long *prediv, unsigned long *div)
+{
+	if (rate < parent_rate / 32UL)
+		*prediv = 32UL;
+	else
+		*prediv = 2UL;
+
+	parent_rate /= *prediv;
+	*div = parent_rate / rate;
+}
+
+static int ad242x_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct ad242x_clk_hw *clk_hw = to_ad242x_clk(hw);
+	unsigned long pll_rate = parent_rate * 2048UL;
+	unsigned long prediv, div;
+	unsigned int val = 0;
+
+	if (rate > pll_rate / 4 || rate < pll_rate / 1024UL)
+		return -EINVAL;
+
+	ad242x_do_div(rate, pll_rate, &prediv, &div);
+
+	if (prediv == 32UL)
+		val |= AD242X_CLKCFG_PDIV32;
+
+	val |= AD242X_CLKCFG_DIV((div / 2UL) - 1UL);
+
+	return regmap_update_bits(clk_hw->node->regmap, clk_hw->reg,
+				  AD242X_CLKCFG_DIVMSK | AD242X_CLKCFG_PDIV32,
+				  val);
+}
+
+static unsigned long ad242x_clk_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct ad242x_clk_hw *clk_hw = to_ad242x_clk(hw);
+	unsigned long pll_rate = parent_rate * 2048UL;
+	unsigned long prediv, div;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(clk_hw->node->regmap, clk_hw->reg, &val);
+	if (ret < 0)
+		return ret;
+
+	prediv = (val & AD242X_CLKCFG_PDIV32) ? 32UL : 2UL;
+	div = 2UL * ((val & AD242X_CLKCFG_DIVMSK) + 1UL);
+
+	return pll_rate / (prediv * div);
+}
+
+static long ad242x_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	unsigned long pll_rate = *parent_rate * 2048UL;
+	unsigned long prediv, div;
+
+	if (rate > pll_rate / 4 || rate < pll_rate / 1024UL)
+		return -EINVAL;
+
+	ad242x_do_div(rate, pll_rate, &prediv, &div);
+
+	return pll_rate / (prediv * div);
+}
+
+static int ad242x_clk_get_phase(struct clk_hw *hw)
+{
+	struct ad242x_clk_hw *clk_hw = to_ad242x_clk(hw);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(clk_hw->node->regmap, clk_hw->reg, &val);
+	if (ret < 0)
+		return ret;
+
+	return (val & AD242X_CLKCFG_INV) ? 180 : 0;
+}
+
+static int ad242x_clk_set_phase(struct clk_hw *hw, int phase)
+{
+	struct ad242x_clk_hw *clk_hw = to_ad242x_clk(hw);
+	unsigned int val;
+
+	switch (phase) {
+	case 0:
+		val = 0;
+		break;
+	case 180:
+		val = AD242X_CLKCFG_INV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(clk_hw->node->regmap, clk_hw->reg,
+				  AD242X_CLKCFG_INV, val);
+}
+
+static const struct clk_ops ad242x_clk_ops = {
+	.prepare	= ad242x_clk_prepare,
+	.unprepare	= ad242x_clk_unprepare,
+	.get_phase	= ad242x_clk_get_phase,
+	.set_phase	= ad242x_clk_set_phase,
+	.recalc_rate	= ad242x_clk_recalc_rate,
+	.round_rate	= ad242x_clk_round_rate,
+	.set_rate	= ad242x_clk_set_rate,
+};
+
+static struct clk_hw *
+ad242x_of_clk_get(struct of_phandle_args *clkspec, void *data)
+{
+	struct ad242x_clk_driver_data *drvdata = data;
+	unsigned int idx = clkspec->args[0];
+
+	return &drvdata->hw[idx].hw;
+}
+
+static int ad242x_clk_probe(struct platform_device *pdev)
+{
+	const char *clk_names[AD242X_NUM_CLKS] = { "clkout1", "clkout2" };
+	u8 regs[AD242X_NUM_CLKS] = { AD242X_CLK1CFG, AD242X_CLK2CFG };
+	struct ad242x_clk_driver_data *drvdata;
+	struct device *dev = &pdev->dev;
+	const char *sync_clk_name;
+	struct ad242x_node *node;
+	int i, ret;
+
+	if (!dev->of_node)
+		return -ENODEV;
+
+	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	node = dev_get_drvdata(dev->parent);
+	sync_clk_name = ad242x_master_get_clk_name(node->master);
+
+	for (i = 0; i < AD242X_NUM_CLKS; i++) {
+		const char *name;
+
+		if (of_property_read_string_index(dev->of_node,
+						  "clock-output-names",
+						  i, &name) == 0)
+			drvdata->hw[i].init.name = name;
+		else
+			drvdata->hw[i].init.name = clk_names[i];
+
+		drvdata->hw[i].reg = regs[i];
+		drvdata->hw[i].init.ops = &ad242x_clk_ops;
+		drvdata->hw[i].init.num_parents = 1;
+		drvdata->hw[i].init.parent_names = &sync_clk_name;
+		drvdata->hw[i].hw.init = &drvdata->hw[i].init;
+		drvdata->hw[i].node = node;
+
+		ret = devm_clk_hw_register(dev, &drvdata->hw[i].hw);
+		if (ret < 0)
+			return ret;
+	}
+
+	return devm_of_clk_add_hw_provider(dev, ad242x_of_clk_get, drvdata);
+}
+
+static const struct of_device_id ad242x_dt_ids[] = {
+	{ .compatible = "adi,ad2428w-clk", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, ad242x_dt_ids);
+
+static struct platform_driver ad242x_clk_driver = {
+	.probe = ad242x_clk_probe,
+	.driver = {
+		.name = "ad242x-clk",
+		.of_match_table	= ad242x_dt_ids,
+	},
+};
+module_platform_driver(ad242x_clk_driver);
+
+MODULE_AUTHOR("Daniel Mack <daniel@zonque.org>");
+MODULE_DESCRIPTION("AD242x clock driver");
+MODULE_LICENSE("GPL v2");