diff --git a/doc/device-tree-bindings/clock/ti,cdce9xx.txt b/doc/device-tree-bindings/clock/ti,cdce9xx.txt new file mode 100644 index 0000000000000000000000000000000000000000..0d01f2d5cc36e8cd4aa636f365ed55482c50d1d1 --- /dev/null +++ b/doc/device-tree-bindings/clock/ti,cdce9xx.txt @@ -0,0 +1,49 @@ +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; + }; + }; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 0035f0a9c6410503f8a0cf41e2400f7d995d5a41..16d4237f8919de7302c4cba5dcd2803d0ae46fde 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -134,6 +134,13 @@ config CLK_STM32MP1 Enable the STM32 clock (RCC) driver. Enable support for manipulating STM32MP1's on-SoC clocks. +config CLK_CDCE9XX + bool "Enable CDCD9XX clock driver" + depends on CLK + help + Enable the clock synthesizer driver for CDCE913/925/937/949 + series of chips. + source "drivers/clk/analogbits/Kconfig" source "drivers/clk/at91/Kconfig" source "drivers/clk/exynos/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d7cea3b8bf22db2cd75200fe2f145abe44975f9b..8de67774680069679ccd277eb9cfa09918f5f2d7 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -44,3 +44,4 @@ obj-$(CONFIG_SANDBOX_CLK_CCF) += clk_sandbox_ccf.o obj-$(CONFIG_STM32H7) += clk_stm32h7.o obj-$(CONFIG_CLK_TI_SCI) += clk-ti-sci.o obj-$(CONFIG_CLK_VERSAL) += clk_versal.o +obj-$(CONFIG_CLK_CDCE9XX) += clk-cdce9xx.o diff --git a/drivers/clk/clk-cdce9xx.c b/drivers/clk/clk-cdce9xx.c new file mode 100644 index 0000000000000000000000000000000000000000..5d1489ab0ec060b4dca95281892b987be8724f52 --- /dev/null +++ b/drivers/clk/clk-cdce9xx.c @@ -0,0 +1,254 @@ +// 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 + * + * Based on Linux kernel clk-cdce925.c. + */ + +#include +#include +#include +#include +#include + +#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, +};