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

From: Heiko Stuebner
Date: Mon Jul 15 2024 - 07:04:44 EST


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@xxxxxxxxx>
---
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

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@xxxxxxxxx>
+ *
+ * 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@xxxxxxxxx>");
+MODULE_DESCRIPTION("Voltage controlled oscillator driver");
+MODULE_LICENSE("GPL");
--
2.39.2