From f0bcbe6c180485a9ff373cbe24cdaa415228a844 Mon Sep 17 00:00:00 2001 From: Mario Six Date: Fri, 27 Apr 2018 14:53:15 +0200 Subject: [PATCH] clk: Add ICS8N3QV01 driver Add a driver for the ICS8N3QV01 Quad-Frequency Programmable VCXO. Signed-off-by: Mario Six --- drivers/clk/Kconfig | 8 ++ drivers/clk/Makefile | 1 + drivers/clk/ics8n3qv01.c | 230 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 drivers/clk/ics8n3qv01.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index c382e8865f..60a6859c24 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -89,4 +89,12 @@ source "drivers/clk/exynos/Kconfig" source "drivers/clk/at91/Kconfig" source "drivers/clk/renesas/Kconfig" +config ICS8N3QV01 + bool "Enable ICS8N3QV01 VCXO driver" + depends on CLK + help + Support for the ICS8N3QV01 Quad-Frequency VCXO (Voltage-Controlled + Crystal Oscillator). The output frequency can be programmed via an + I2C interface. + endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index e05c607223..0b4f38527a 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_CLK_STM32MP1) += clk_stm32mp1.o obj-$(CONFIG_CLK_UNIPHIER) += uniphier/ obj-$(CONFIG_CLK_ZYNQ) += clk_zynq.o obj-$(CONFIG_CLK_ZYNQMP) += clk_zynqmp.o +obj-$(CONFIG_ICS8N3QV01) += ics8n3qv01.o obj-$(CONFIG_MACH_PIC32) += clk_pic32.o obj-$(CONFIG_SANDBOX) += clk_sandbox.o obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o diff --git a/drivers/clk/ics8n3qv01.c b/drivers/clk/ics8n3qv01.c new file mode 100644 index 0000000000..e85ab1f9a0 --- /dev/null +++ b/drivers/clk/ics8n3qv01.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (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 + */ + +#include +#include +#include +#include + +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; + +const uint MAX_FREQ_INDEX = 3; + +struct ics8n3qv01_priv { + ulong rate; +}; + +static int ics8n3qv01_get_fout_calc(struct udevice *dev, uint index, + uint *fout_calc) +{ + u64 n, mint, mfrac; + u8 reg_a, reg_b, reg_c, reg_d, reg_f; + int val[6]; + int i; + + if (index > MAX_FREQ_INDEX) + return -EINVAL; + + for (i = 0; i <= 5; ++i) { + u8 tmp = dm_i2c_reg_read(dev, 4 * i + index); + + if (tmp < 0) { + debug("%s: Error while reading i2c register %d.\n", + dev->name, 4 * i + index); + return tmp; + } + + val[i] = tmp; + } + + reg_a = val[0]; /* Register 0 + index */ + reg_b = val[1]; /* Register 4 + index */ + reg_c = val[2]; /* Register 8 + index */ + reg_d = val[3]; /* Register 12 + index */ + reg_f = val[5]; /* Register 20 + index */ + + mint = ((reg_a >> 1) & 0x1f) | /* MINTi[4-0]*/ + (reg_f & 0x20); /* MINTi[5] */ + mfrac = ((reg_a & 0x01) << 17) | /* MFRACi[17] */ + (reg_b << 9) | /* MFRACi[16-9] */ + (reg_c << 1) | /* MFRACi[8-1] */ + (reg_d >> 7); /* MFRACi[0] */ + n = reg_d & 0x7f; /* Ni[6-0] */ + + *fout_calc = (mint * ICS8N3QV01_FREF_LL + + mfrac * ICS8N3QV01_FREF_LL / 262144LL + + ICS8N3QV01_FREF_LL / 524288LL + + n / 2) + / n + * 1000000 + / (1000000 - 100); + + return 0; +} + +static int ics8n3qv01_calc_parameters(uint fout, uint *_mint, uint *_mfrac, + uint *_n) +{ + uint n, foutiic, fvcoiic, mint; + u64 mfrac; + + n = (2215000000U + fout / 2) / fout; + if (fout < 417000000U) + n = 2 * ((2215000000U / 2 + fout / 2) / fout); + else + 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) + return -EINVAL; + + mfrac = ((u64)fvcoiic % 114285000LL) * 262144LL + / 114285000LL; + + *_mint = mint; + *_mfrac = mfrac; + *_n = n; + + return 0; +} + +static ulong ics8n3qv01_set_rate(struct clk *clk, ulong fout) +{ + struct ics8n3qv01_priv *priv = dev_get_priv(clk->dev); + uint n, mint, mfrac; + uint fout_calc = 0; + u64 fout_prog; + long long off_ppm; + int res, i; + u8 reg[6]; + int tmp; + int addr[] = {0, 4, 8, 12, 18, 20}; + + priv->rate = fout; + + res = ics8n3qv01_get_fout_calc(clk->dev, 1, &fout_calc); + + if (res) { + debug("%s: Error during output frequency calculation.\n", + clk->dev->name); + return res; + } + + 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; + res = ics8n3qv01_calc_parameters(fout_prog, &mint, &mfrac, &n); + + if (res) { + debug("%s: Cannot determine mint parameter.\n", + clk->dev->name); + return res; + } + + /* Register 0 */ + tmp = dm_i2c_reg_read(clk->dev, 0) & 0xc0; + if (tmp < 0) + return tmp; + reg[0] = tmp | (mint & 0x1f) << 1; + reg[0] |= (mfrac >> 17) & 0x01; + + /* Register 4 */ + reg[1] = mfrac >> 9; + + /* Register 8 */ + reg[2] = mfrac >> 1; + + /* Register 12 */ + reg[3] = mfrac << 7; + reg[3] |= n & 0x7f; + + /* Register 18 */ + tmp = dm_i2c_reg_read(clk->dev, 18) & 0x03; + if (tmp < 0) + return tmp; + reg[4] = tmp | 0x20; + + /* Register 20 */ + tmp = dm_i2c_reg_read(clk->dev, 20) & 0x1f; + if (tmp < 0) + return tmp; + reg[5] = tmp | (mint & (1 << 5)); + + for (i = 0; i <= 5; ++i) { + res = dm_i2c_reg_write(clk->dev, addr[i], reg[i]); + if (res < 0) + return res; + } + + 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) +{ + 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), +}; -- 2.25.1