
Add a driver for the ICS8N3QV01 Quad-Frequency Programmable VCXO.
Signed-off-by: Mario Six mario.six@gdsys.cc ---
drivers/clk/Kconfig | 6 ++ drivers/clk/Makefile | 1 + drivers/clk/ics8n3qv01.c | 184 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 drivers/clk/ics8n3qv01.c
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 44da716b26..f6f3810b64 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -56,4 +56,10 @@ source "drivers/clk/uniphier/Kconfig" source "drivers/clk/exynos/Kconfig" source "drivers/clk/at91/Kconfig"
+config ICS8N3QV01 + bool "Enable ICS8N3QV01 VCXO driver" + depends on CLK + help + Support for the ICS8N3QV01 VCXO. + endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 2746a8016a..d7cc486d23 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o obj-$(CONFIG_CLK_BOSTON) += clk_boston.o obj-$(CONFIG_ARCH_ASPEED) += aspeed/ obj-$(CONFIG_STM32F7) += clk_stm32f7.o +obj-$(CONFIG_ICS8N3QV01) += ics8n3qv01.o diff --git a/drivers/clk/ics8n3qv01.c b/drivers/clk/ics8n3qv01.c new file mode 100644 index 0000000000..f5f4b74982 --- /dev/null +++ b/drivers/clk/ics8n3qv01.c @@ -0,0 +1,184 @@ +/* + * (C) Copyright 2017 + * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc + * + * based on the gdsys osd driver, which is + * + * (C) Copyright 2010 + * Dirk Eibach, Guntermann & Drunck GmbH, eibach@gdsys.de + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <clk-uclass.h> +#include <i2c.h> + +const long long ICS8N3QV01_FREF = 114285000; +const long long ICS8N3QV01_FREF_LL = 114285000LL; +const long long ICS8N3QV01_F_DEFAULT_0 = 156250000LL; +const long long ICS8N3QV01_F_DEFAULT_1 = 125000000LL; +const long long ICS8N3QV01_F_DEFAULT_2 = 100000000LL; +const long long ICS8N3QV01_F_DEFAULT_3 = 25175000LL; + +struct ics8n3qv01_priv { + ulong rate; +}; + +static uint ics8n3qv01_get_fout_calc(struct udevice *dev, uint index) +{ + u64 n, mint, mfrac; + u8 reg_a, reg_b, reg_c, reg_d, reg_f; + u64 fout_calc; + + if (index > 3) + return 0; + + reg_a = dm_i2c_reg_read(dev, 0 + index); + reg_b = dm_i2c_reg_read(dev, 4 + index); + reg_c = dm_i2c_reg_read(dev, 8 + index); + reg_d = dm_i2c_reg_read(dev, 12 + index); + reg_f = dm_i2c_reg_read(dev, 20 + index); + + mint = ((reg_a >> 1) & 0x1f) | (reg_f & 0x20); + mfrac = ((reg_a & 0x01) << 17) | (reg_b << 9) | (reg_c << 1) + | (reg_d >> 7); + n = reg_d & 0x7f; + + fout_calc = (mint * ICS8N3QV01_FREF_LL + + mfrac * ICS8N3QV01_FREF_LL / 262144LL + + ICS8N3QV01_FREF_LL / 524288LL + + n / 2) + / n + * 1000000 + / (1000000 - 100); + + return fout_calc; +} + +static void ics8n3qv01_calc_parameters(uint fout, uint *_mint, uint *_mfrac, + uint *_n) +{ + uint n, foutiic, fvcoiic, mint; + u64 mfrac; + + n = (2215000000U + fout / 2) / fout; + if ((n & 1) && (n > 5)) + n -= 1; + + foutiic = fout - (fout / 10000); + fvcoiic = foutiic * n; + + mint = fvcoiic / 114285000; + if ((mint < 17) || (mint > 63)) + printf("ics8n3qv01_calc_parameters: cannot determine mint\n"); + + mfrac = ((u64)fvcoiic % 114285000LL) * 262144LL + / 114285000LL; + + *_mint = mint; + *_mfrac = mfrac; + *_n = n; +} + +static ulong ics8n3qv01_set_rate(struct clk *clk, ulong fout) +{ + struct ics8n3qv01_priv *priv = dev_get_priv(clk->dev); + uint n, mint, mfrac, fout_calc; + u64 fout_prog; + long long off_ppm; + u8 reg0, reg4, reg8, reg12, reg18, reg20; + + priv->rate = fout; + + fout_calc = ics8n3qv01_get_fout_calc(clk->dev, 1); + off_ppm = (fout_calc - ICS8N3QV01_F_DEFAULT_1) * 1000000 + / ICS8N3QV01_F_DEFAULT_1; + printf("%s: PLL is off by %lld ppm\n", clk->dev->name, off_ppm); + fout_prog = (u64)fout * (u64)fout_calc + / ICS8N3QV01_F_DEFAULT_1; + ics8n3qv01_calc_parameters(fout_prog, &mint, &mfrac, &n); + + reg0 = dm_i2c_reg_read(clk->dev, 0) & 0xc0; + reg0 |= (mint & 0x1f) << 1; + reg0 |= (mfrac >> 17) & 0x01; + dm_i2c_reg_write(clk->dev, 0, reg0); + + reg4 = mfrac >> 9; + dm_i2c_reg_write(clk->dev, 4, reg4); + + reg8 = mfrac >> 1; + dm_i2c_reg_write(clk->dev, 8, reg8); + + reg12 = mfrac << 7; + reg12 |= n & 0x7f; + dm_i2c_reg_write(clk->dev, 12, reg12); + + reg18 = dm_i2c_reg_read(clk->dev, 18) & 0x03; + reg18 |= 0x20; + dm_i2c_reg_write(clk->dev, 18, reg18); + + reg20 = dm_i2c_reg_read(clk->dev, 20) & 0x1f; + reg20 |= mint & (1 << 5); + dm_i2c_reg_write(clk->dev, 20, reg20); + + return 0; +} + +static int ics8n3qv01_request(struct clk *clock) +{ + return 0; +} + +static ulong ics8n3qv01_get_rate(struct clk *clk) +{ + struct ics8n3qv01_priv *priv = dev_get_priv(clk->dev); + + return priv->rate; +} + +static int ics8n3qv01_enable(struct clk *clk) +{ + return 0; +} + +static int ics8n3qv01_disable(struct clk *clk) +{ + return 0; +} + +static const struct clk_ops ics8n3qv01_ops = { + .request = ics8n3qv01_request, + .get_rate = ics8n3qv01_get_rate, + .set_rate = ics8n3qv01_set_rate, + .enable = ics8n3qv01_enable, + .disable = ics8n3qv01_disable, +}; + +static const struct udevice_id ics8n3qv01_ids[] = { + { .compatible = "idt,ics8n3qv01" }, + { /* sentinel */ } +}; + +int ics8n3qv01_probe(struct udevice *dev) +{ + struct dm_i2c_chip *chip = dev_get_parent_platdata(dev); + struct udevice *dummy; + + if (dm_i2c_probe(dev->parent, chip->chip_addr, chip->flags, &dummy)) { + printf("ics8n3qv01: I2C probe did not succeed.\n"); + return -1; + } + + return 0; +} + +U_BOOT_DRIVER(ics8n3qv01) = { + .name = "ics8n3qv01", + .id = UCLASS_CLK, + .ops = &ics8n3qv01_ops, + .of_match = ics8n3qv01_ids, + .probe = ics8n3qv01_probe, + .priv_auto_alloc_size = sizeof(struct ics8n3qv01_priv), +};