--- /dev/null
+Binding for TI CDCE913/925/937/949 programmable I2C clock synthesizers.
+
+Reference
+This binding uses the common clock binding[1].
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+[2] http://www.ti.com/product/cdce913
+[3] http://www.ti.com/product/cdce925
+[4] http://www.ti.com/product/cdce937
+[5] http://www.ti.com/product/cdce949
+
+The driver provides clock sources for each output Y1 through Y5.
+
+Required properties:
+ - compatible: Shall be one of the following:
+ - "ti,cdce913": 1-PLL, 3 Outputs
+ - "ti,cdce925": 2-PLL, 5 Outputs
+ - "ti,cdce937": 3-PLL, 7 Outputs
+ - "ti,cdce949": 4-PLL, 9 Outputs
+ - reg: I2C device address.
+ - clocks: Points to a fixed parent clock that provides the input frequency.
+ - #clock-cells: From common clock bindings: Shall be 1.
+
+Optional properties:
+ - xtal-load-pf: Crystal load-capacitor value to fine-tune performance on a
+ board, or to compensate for external influences.
+
+For all PLL1, PLL2, ... an optional child node can be used to specify spread
+spectrum clocking parameters for a board.
+ - spread-spectrum: SSC mode as defined in the data sheet.
+ - spread-spectrum-center: Use "centered" mode instead of "max" mode. When
+ present, the clock runs at the requested frequency on average. Otherwise
+ the requested frequency is the maximum value of the SCC range.
+
+
+Example:
+
+ clockgen: cdce925pw@64 {
+ compatible = "cdce925";
+ reg = <0x64>;
+ clocks = <&xtal_27Mhz>;
+ #clock-cells = <1>;
+ xtal-load-pf = <5>;
+ /* PLL options to get SSC 1% centered */
+ PLL2 {
+ spread-spectrum = <4>;
+ spread-spectrum-center;
+ };
+ };
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Texas Instruments CDCE913/925/937/949 clock synthesizer driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Tero Kristo <t-kristo@ti.com>
+ *
+ * Based on Linux kernel clk-cdce925.c.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <clk-uclass.h>
+#include <i2c.h>
+
+#define MAX_NUMBER_OF_PLLS 4
+#define MAX_NUMER_OF_OUTPUTS 9
+
+#define CDCE9XX_REG_GLOBAL1 0x01
+#define CDCE9XX_REG_Y1SPIPDIVH 0x02
+#define CDCE9XX_REG_PDIV1L 0x03
+#define CDCE9XX_REG_XCSEL 0x05
+
+#define CDCE9XX_PDIV1_H_MASK 0x3
+
+#define CDCE9XX_REG_PDIV(clk) (0x16 + (((clk) - 1) & 1) + \
+ ((clk) - 1) / 2 * 0x10)
+
+#define CDCE9XX_PDIV_MASK 0x7f
+
+#define CDCE9XX_BYTE_TRANSFER BIT(7)
+
+struct cdce9xx_chip_info {
+ int num_plls;
+ int num_outputs;
+};
+
+struct cdce9xx_clk_data {
+ struct udevice *i2c;
+ struct cdce9xx_chip_info *chip;
+ u32 xtal_rate;
+};
+
+static const struct cdce9xx_chip_info cdce913_chip_info = {
+ .num_plls = 1, .num_outputs = 3,
+};
+
+static const struct cdce9xx_chip_info cdce925_chip_info = {
+ .num_plls = 2, .num_outputs = 5,
+};
+
+static const struct cdce9xx_chip_info cdce937_chip_info = {
+ .num_plls = 3, .num_outputs = 7,
+};
+
+static const struct cdce9xx_chip_info cdce949_chip_info = {
+ .num_plls = 4, .num_outputs = 9,
+};
+
+static int cdce9xx_reg_read(struct udevice *dev, u8 addr, u8 *buf)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(dev);
+ int ret;
+
+ ret = dm_i2c_read(data->i2c, addr | CDCE9XX_BYTE_TRANSFER, buf, 1);
+ if (ret)
+ dev_err(dev, "%s: failed for addr:%x, ret:%d\n", __func__,
+ addr, ret);
+
+ return ret;
+}
+
+static int cdce9xx_reg_write(struct udevice *dev, u8 addr, u8 val)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(dev);
+ int ret;
+
+ ret = dm_i2c_write(data->i2c, addr | CDCE9XX_BYTE_TRANSFER, &val, 1);
+ if (ret)
+ dev_err(dev, "%s: failed for addr:%x, ret:%d\n", __func__,
+ addr, ret);
+
+ return ret;
+}
+
+static int cdce9xx_clk_of_xlate(struct clk *clk,
+ struct ofnode_phandle_args *args)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(clk->dev);
+
+ if (args->args_count != 1)
+ return -EINVAL;
+
+ if (args->args[0] > data->chip->num_outputs)
+ return -EINVAL;
+
+ clk->id = args->args[0];
+
+ return 0;
+}
+
+static int cdce9xx_clk_probe(struct udevice *dev)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(dev);
+ struct cdce9xx_chip_info *chip = (void *)dev_get_driver_data(dev);
+ int ret;
+ u32 val;
+ struct clk clk;
+
+ val = (u32)dev_read_addr_ptr(dev);
+
+ ret = i2c_get_chip(dev->parent, val, 1, &data->i2c);
+ if (ret) {
+ dev_err(dev, "I2C probe failed.\n");
+ return ret;
+ }
+
+ data->chip = chip;
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ data->xtal_rate = clk_get_rate(&clk);
+
+ val = dev_read_u32_default(dev, "xtal-load-pf", -1);
+ if (val >= 0)
+ cdce9xx_reg_write(dev, CDCE9XX_REG_XCSEL, val << 3);
+
+ return 0;
+}
+
+static u16 cdce9xx_clk_get_pdiv(struct clk *clk)
+{
+ u8 val;
+ u16 pdiv;
+ int ret;
+
+ if (clk->id == 0) {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, &val);
+ if (ret)
+ return 0;
+
+ pdiv = (val & CDCE9XX_PDIV1_H_MASK) << 8;
+
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV1L, &val);
+ if (ret)
+ return 0;
+
+ pdiv |= val;
+ } else {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV(clk->id),
+ &val);
+ if (ret)
+ return 0;
+
+ pdiv = val & CDCE9XX_PDIV_MASK;
+ }
+
+ return pdiv;
+}
+
+static u32 cdce9xx_clk_get_parent_rate(struct clk *clk)
+{
+ struct cdce9xx_clk_data *data = dev_get_priv(clk->dev);
+
+ return data->xtal_rate;
+}
+
+static ulong cdce9xx_clk_get_rate(struct clk *clk)
+{
+ u32 parent_rate;
+ u16 pdiv;
+
+ parent_rate = cdce9xx_clk_get_parent_rate(clk);
+
+ pdiv = cdce9xx_clk_get_pdiv(clk);
+
+ return parent_rate / pdiv;
+}
+
+static ulong cdce9xx_clk_set_rate(struct clk *clk, ulong rate)
+{
+ u32 parent_rate;
+ int pdiv;
+ u32 diff;
+ u8 val;
+ int ret;
+
+ parent_rate = cdce9xx_clk_get_parent_rate(clk);
+
+ pdiv = parent_rate / rate;
+
+ diff = rate - parent_rate / pdiv;
+
+ if (rate - parent_rate / (pdiv + 1) < diff)
+ pdiv++;
+
+ if (clk->id == 0) {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, &val);
+ if (ret)
+ return ret;
+
+ val &= ~CDCE9XX_PDIV1_H_MASK;
+
+ val |= (pdiv >> 8);
+
+ ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_Y1SPIPDIVH, val);
+ if (ret)
+ return ret;
+
+ ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_PDIV1L,
+ (pdiv & 0xff));
+ if (ret)
+ return ret;
+ } else {
+ ret = cdce9xx_reg_read(clk->dev, CDCE9XX_REG_PDIV(clk->id),
+ &val);
+ if (ret)
+ return ret;
+
+ val &= ~CDCE9XX_PDIV_MASK;
+
+ val |= pdiv;
+
+ ret = cdce9xx_reg_write(clk->dev, CDCE9XX_REG_PDIV(clk->id),
+ val);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id cdce9xx_clk_of_match[] = {
+ { .compatible = "ti,cdce913", .data = (u32)&cdce913_chip_info },
+ { .compatible = "ti,cdce925", .data = (u32)&cdce925_chip_info },
+ { .compatible = "ti,cdce937", .data = (u32)&cdce937_chip_info },
+ { .compatible = "ti,cdce949", .data = (u32)&cdce949_chip_info },
+ { /* sentinel */ },
+};
+
+static const struct clk_ops cdce9xx_clk_ops = {
+ .of_xlate = cdce9xx_clk_of_xlate,
+ .get_rate = cdce9xx_clk_get_rate,
+ .set_rate = cdce9xx_clk_set_rate,
+};
+
+U_BOOT_DRIVER(cdce9xx_clk) = {
+ .name = "cdce9xx-clk",
+ .id = UCLASS_CLK,
+ .of_match = cdce9xx_clk_of_match,
+ .probe = cdce9xx_clk_probe,
+ .priv_auto_alloc_size = sizeof(struct cdce9xx_clk_data),
+ .ops = &cdce9xx_clk_ops,
+};