| // 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 <dm.h> |
| #include <clk-uclass.h> |
| #include <i2c.h> |
| #include <log.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; |
| |
| 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; |
| |
| 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 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 = { |
| .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 = sizeof(struct ics8n3qv01_priv), |
| }; |