diff mbox series

[v2,2/3] clk: add driver for voltage controlled oscillators

Message ID 20240715110251.261844-3-heiko@sntech.de (mailing list archive)
State New
Headers show
Series Binding and driver for voltage controlled oscillators | expand

Commit Message

Heiko Stuebner July 15, 2024, 11:02 a.m. UTC
In contrast to fixed clocks that are described as ungateable, boards
sometimes use additional oscillators for things like PCIe reference
clocks, that need actual supplies to get enabled and enable-gpios to be
toggled for them to work.

This adds a driver for those generic voltage controlled oscillators,
that can show up in schematics looking like

         ----------------
Enable - | 100MHz,3.3V, | - VDD
         |    3225      |
   GND - |              | - OUT
         ----------------

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
---
 drivers/clk/Kconfig   |  10 ++++
 drivers/clk/Makefile  |   1 +
 drivers/clk/clk-vco.c | 133 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 144 insertions(+)
 create mode 100644 drivers/clk/clk-vco.c

Comments

Stephen Boyd July 26, 2024, 10:39 p.m. UTC | #1
Quoting Heiko Stuebner (2024-07-15 04:02:50)
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index 4abe16c8ccdfe..ca7b7b7ddfd8d 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -79,6 +79,7 @@ obj-$(CONFIG_COMMON_CLK_SI521XX)      += clk-si521xx.o
>  obj-$(CONFIG_COMMON_CLK_VC3)           += clk-versaclock3.o
>  obj-$(CONFIG_COMMON_CLK_VC5)           += clk-versaclock5.o
>  obj-$(CONFIG_COMMON_CLK_VC7)           += clk-versaclock7.o
> +obj-$(CONFIG_COMMON_CLK_VCO)           += clk-vco.o

Wrong section. It's basically a common clk type.

>  obj-$(CONFIG_COMMON_CLK_WM831X)                += clk-wm831x.o
>  obj-$(CONFIG_COMMON_CLK_XGENE)         += clk-xgene.o
>  
> diff --git a/drivers/clk/clk-vco.c b/drivers/clk/clk-vco.c
> new file mode 100644
> index 0000000000000..f7fe2bc627f36
> --- /dev/null
> +++ b/drivers/clk/clk-vco.c
> @@ -0,0 +1,133 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
> + *
> + * Generic voltage controlled oscillator
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +
> +struct clk_vco {
> +       struct device *dev;
> +       struct clk_hw hw;
> +       u32 rate;
> +       struct regulator *supply;
> +       struct gpio_desc *enable_gpio;
> +};
> +
> +#define to_clk_vco(_hw) container_of(_hw, struct clk_vco, hw)
> +
> +static int clk_vco_prepare(struct clk_hw *hw)
> +{
> +       return regulator_enable(to_clk_vco(hw)->supply);
> +}
> +
> +static void clk_vco_unprepare(struct clk_hw *hw)
> +{
> +       regulator_disable(to_clk_vco(hw)->supply);
> +}
> +
> +static int clk_vco_enable(struct clk_hw *hw)
> +{
> +       gpiod_set_value(to_clk_vco(hw)->enable_gpio, 1);
> +       return 0;
> +}
> +
> +static void clk_vco_disable(struct clk_hw *hw)
> +{
> +       gpiod_set_value(to_clk_vco(hw)->enable_gpio, 0);
> +}

It looks similar to clk-gpio.c code, but not as complete because it
assumes gpios can't sleep. Please look into reusing that code somehow,
possibly exporting 'clk_gpio_gate_ops' and struct clk_gpio for use in
this new driver. It would be good to fold the sleepable gpio bit as well
somehow, maybe with a new function to get a device's gpiod along with
returning a const pointer to the clk_ops that can be copied and amended
with the regulator part.

> +
> +static unsigned long clk_vco_recalc_rate(struct clk_hw *hw,
> +                                              unsigned long parent_rate)
> +{
> +       return to_clk_vco(hw)->rate;
> +}
> +
> +const struct clk_ops clk_vco_ops = {
> +       .prepare = clk_vco_prepare,
> +       .unprepare = clk_vco_unprepare,
> +       .enable = clk_vco_enable,
> +       .disable = clk_vco_disable,
> +       .recalc_rate = clk_vco_recalc_rate,
> +};
> +
> +static int clk_vco_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct clk_vco *clkgen;
> +       const char *clk_name;
> +       int ret;
> +
> +       clkgen = devm_kzalloc(dev, sizeof(*clkgen), GFP_KERNEL);
> +       if (!clkgen)
> +               return -ENOMEM;
> +
> +       clkgen->dev = dev;

Is this used outside of probe? Why stash it?

> +
> +       if (device_property_read_u32(dev, "clock-frequency", &clkgen->rate))
> +               return dev_err_probe(dev, -EIO, "failed to get clock-frequency");
> +
> +       ret = device_property_read_string(dev, "clock-output-names", &clk_name);
> +       if (ret)
> +               clk_name = fwnode_get_name(dev->fwnode);
> +
> +       clkgen->supply = devm_regulator_get_optional(dev, "vdd");
> +       if (IS_ERR(clkgen->supply)) {
> +               if (PTR_ERR(clkgen->supply) != -ENODEV)
> +                       return dev_err_probe(dev, PTR_ERR(clkgen->supply),
> +                                            "failed to get regulator\n");
> +               clkgen->supply = NULL;
> +       }
> +
> +       clkgen->enable_gpio = devm_gpiod_get_optional(dev, "enable",
> +                                                     GPIOD_OUT_LOW);
> +       if (IS_ERR(clkgen->enable_gpio))
> +               return dev_err_probe(dev, PTR_ERR(clkgen->enable_gpio),
> +                                    "failed to get gpio\n");
> +
> +       ret = gpiod_direction_output(clkgen->enable_gpio, 0);
> +       if (ret < 0)
> +               return dev_err_probe(dev, ret, "failed to set gpio output");

Missing newline.

> +
> +       clkgen->hw.init = CLK_HW_INIT_NO_PARENT(clk_name, &clk_vco_ops, 0);
> +
> +       /* register the clock */
> +       ret = devm_clk_hw_register(dev, &clkgen->hw);
> +       if (ret)
> +               return dev_err_probe(dev, ret,
> +                                    "failed to register clock\n");
> +
diff mbox series

Patch

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 3e9099504fad4..e93a380b6ee47 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -414,6 +414,16 @@  config COMMON_CLK_VC7
 	  Renesas Versaclock7 is a family of configurable clock generator
 	  and jitter attenuator ICs with fractional and integer dividers.
 
+config COMMON_CLK_VCO
+	tristate "Clock driver for voltage controlled oscillators"
+	depends on GPIOLIB && REGULATOR
+	help
+	  This driver supports generic voltage controlled oscillators that
+	  are not configurable but need supplies to be enabled to run.
+	  Generally they need a supply voltage to be enabled and may also
+	  require a separate enable pin, though in a lot of cases, vdd
+	  and enable control might be tied to the same supply.
+
 config COMMON_CLK_STM32F
 	def_bool COMMON_CLK && (MACH_STM32F429 || MACH_STM32F469 || MACH_STM32F746)
 	help
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 4abe16c8ccdfe..ca7b7b7ddfd8d 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -79,6 +79,7 @@  obj-$(CONFIG_COMMON_CLK_SI521XX)	+= clk-si521xx.o
 obj-$(CONFIG_COMMON_CLK_VC3)		+= clk-versaclock3.o
 obj-$(CONFIG_COMMON_CLK_VC5)		+= clk-versaclock5.o
 obj-$(CONFIG_COMMON_CLK_VC7)		+= clk-versaclock7.o
+obj-$(CONFIG_COMMON_CLK_VCO)		+= clk-vco.o
 obj-$(CONFIG_COMMON_CLK_WM831X)		+= clk-wm831x.o
 obj-$(CONFIG_COMMON_CLK_XGENE)		+= clk-xgene.o
 
diff --git a/drivers/clk/clk-vco.c b/drivers/clk/clk-vco.c
new file mode 100644
index 0000000000000..f7fe2bc627f36
--- /dev/null
+++ b/drivers/clk/clk-vco.c
@@ -0,0 +1,133 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
+ *
+ * Generic voltage controlled oscillator
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+struct clk_vco {
+	struct device *dev;
+	struct clk_hw hw;
+	u32 rate;
+	struct regulator *supply;
+	struct gpio_desc *enable_gpio;
+};
+
+#define to_clk_vco(_hw) container_of(_hw, struct clk_vco, hw)
+
+static int clk_vco_prepare(struct clk_hw *hw)
+{
+	return regulator_enable(to_clk_vco(hw)->supply);
+}
+
+static void clk_vco_unprepare(struct clk_hw *hw)
+{
+	regulator_disable(to_clk_vco(hw)->supply);
+}
+
+static int clk_vco_enable(struct clk_hw *hw)
+{
+	gpiod_set_value(to_clk_vco(hw)->enable_gpio, 1);
+	return 0;
+}
+
+static void clk_vco_disable(struct clk_hw *hw)
+{
+	gpiod_set_value(to_clk_vco(hw)->enable_gpio, 0);
+}
+
+static unsigned long clk_vco_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	return to_clk_vco(hw)->rate;
+}
+
+const struct clk_ops clk_vco_ops = {
+	.prepare = clk_vco_prepare,
+	.unprepare = clk_vco_unprepare,
+	.enable = clk_vco_enable,
+	.disable = clk_vco_disable,
+	.recalc_rate = clk_vco_recalc_rate,
+};
+
+static int clk_vco_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct clk_vco *clkgen;
+	const char *clk_name;
+	int ret;
+
+	clkgen = devm_kzalloc(dev, sizeof(*clkgen), GFP_KERNEL);
+	if (!clkgen)
+		return -ENOMEM;
+
+	clkgen->dev = dev;
+
+	if (device_property_read_u32(dev, "clock-frequency", &clkgen->rate))
+		return dev_err_probe(dev, -EIO, "failed to get clock-frequency");
+
+	ret = device_property_read_string(dev, "clock-output-names", &clk_name);
+	if (ret)
+		clk_name = fwnode_get_name(dev->fwnode);
+
+	clkgen->supply = devm_regulator_get_optional(dev, "vdd");
+	if (IS_ERR(clkgen->supply)) {
+		if (PTR_ERR(clkgen->supply) != -ENODEV)
+			return dev_err_probe(dev, PTR_ERR(clkgen->supply),
+					     "failed to get regulator\n");
+		clkgen->supply = NULL;
+	}
+
+	clkgen->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+						      GPIOD_OUT_LOW);
+	if (IS_ERR(clkgen->enable_gpio))
+		return dev_err_probe(dev, PTR_ERR(clkgen->enable_gpio),
+				     "failed to get gpio\n");
+
+	ret = gpiod_direction_output(clkgen->enable_gpio, 0);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "failed to set gpio output");
+
+	clkgen->hw.init = CLK_HW_INIT_NO_PARENT(clk_name, &clk_vco_ops, 0);
+
+	/* register the clock */
+	ret = devm_clk_hw_register(dev, &clkgen->hw);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to register clock\n");
+
+	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+					  &clkgen->hw);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to register clock provider\n");
+
+	return 0;
+}
+
+static const struct of_device_id clk_vco_ids[] = {
+	{ .compatible = "voltage-oscillator" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, clk_vco_ids);
+
+static struct platform_driver clk_vco_driver = {
+	.driver = {
+		.name = "clk_vco",
+		.of_match_table = clk_vco_ids,
+	},
+	.probe = clk_vco_probe,
+};
+module_platform_driver(clk_vco_driver);
+
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("Voltage controlled oscillator driver");
+MODULE_LICENSE("GPL");