Mario Six | f0bcbe6 | 2018-04-27 14:53:15 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * (C) Copyright 2017 |
| 4 | * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc |
| 5 | * |
| 6 | * based on the gdsys osd driver, which is |
| 7 | * |
| 8 | * (C) Copyright 2010 |
| 9 | * Dirk Eibach, Guntermann & Drunck GmbH, eibach@gdsys.de |
| 10 | */ |
| 11 | |
| 12 | #include <common.h> |
| 13 | #include <dm.h> |
| 14 | #include <clk-uclass.h> |
| 15 | #include <i2c.h> |
Simon Glass | f7ae49f | 2020-05-10 11:40:05 -0600 | [diff] [blame] | 16 | #include <log.h> |
Mario Six | f0bcbe6 | 2018-04-27 14:53:15 +0200 | [diff] [blame] | 17 | |
| 18 | const long long ICS8N3QV01_FREF = 114285000; |
| 19 | const long long ICS8N3QV01_FREF_LL = 114285000LL; |
| 20 | const long long ICS8N3QV01_F_DEFAULT_0 = 156250000LL; |
| 21 | const long long ICS8N3QV01_F_DEFAULT_1 = 125000000LL; |
| 22 | const long long ICS8N3QV01_F_DEFAULT_2 = 100000000LL; |
| 23 | const long long ICS8N3QV01_F_DEFAULT_3 = 25175000LL; |
| 24 | |
| 25 | const uint MAX_FREQ_INDEX = 3; |
| 26 | |
| 27 | struct ics8n3qv01_priv { |
| 28 | ulong rate; |
| 29 | }; |
| 30 | |
| 31 | static int ics8n3qv01_get_fout_calc(struct udevice *dev, uint index, |
| 32 | uint *fout_calc) |
| 33 | { |
| 34 | u64 n, mint, mfrac; |
| 35 | u8 reg_a, reg_b, reg_c, reg_d, reg_f; |
| 36 | int val[6]; |
| 37 | int i; |
| 38 | |
| 39 | if (index > MAX_FREQ_INDEX) |
| 40 | return -EINVAL; |
| 41 | |
| 42 | for (i = 0; i <= 5; ++i) { |
| 43 | u8 tmp = dm_i2c_reg_read(dev, 4 * i + index); |
| 44 | |
| 45 | if (tmp < 0) { |
| 46 | debug("%s: Error while reading i2c register %d.\n", |
| 47 | dev->name, 4 * i + index); |
| 48 | return tmp; |
| 49 | } |
| 50 | |
| 51 | val[i] = tmp; |
| 52 | } |
| 53 | |
| 54 | reg_a = val[0]; /* Register 0 + index */ |
| 55 | reg_b = val[1]; /* Register 4 + index */ |
| 56 | reg_c = val[2]; /* Register 8 + index */ |
| 57 | reg_d = val[3]; /* Register 12 + index */ |
| 58 | reg_f = val[5]; /* Register 20 + index */ |
| 59 | |
| 60 | mint = ((reg_a >> 1) & 0x1f) | /* MINTi[4-0]*/ |
| 61 | (reg_f & 0x20); /* MINTi[5] */ |
| 62 | mfrac = ((reg_a & 0x01) << 17) | /* MFRACi[17] */ |
| 63 | (reg_b << 9) | /* MFRACi[16-9] */ |
| 64 | (reg_c << 1) | /* MFRACi[8-1] */ |
| 65 | (reg_d >> 7); /* MFRACi[0] */ |
| 66 | n = reg_d & 0x7f; /* Ni[6-0] */ |
| 67 | |
| 68 | *fout_calc = (mint * ICS8N3QV01_FREF_LL |
| 69 | + mfrac * ICS8N3QV01_FREF_LL / 262144LL |
| 70 | + ICS8N3QV01_FREF_LL / 524288LL |
| 71 | + n / 2) |
| 72 | / n |
| 73 | * 1000000 |
| 74 | / (1000000 - 100); |
| 75 | |
| 76 | return 0; |
| 77 | } |
| 78 | |
| 79 | static int ics8n3qv01_calc_parameters(uint fout, uint *_mint, uint *_mfrac, |
| 80 | uint *_n) |
| 81 | { |
| 82 | uint n, foutiic, fvcoiic, mint; |
| 83 | u64 mfrac; |
| 84 | |
Mario Six | f0bcbe6 | 2018-04-27 14:53:15 +0200 | [diff] [blame] | 85 | if (fout < 417000000U) |
| 86 | n = 2 * ((2215000000U / 2 + fout / 2) / fout); |
| 87 | else |
| 88 | n = (2215000000U + fout / 2) / fout; |
| 89 | |
| 90 | if ((n & 1) && n > 5) |
| 91 | n -= 1; |
| 92 | |
| 93 | foutiic = fout - (fout / 10000); |
| 94 | fvcoiic = foutiic * n; |
| 95 | |
| 96 | mint = fvcoiic / 114285000; |
| 97 | if (mint < 17 || mint > 63) |
| 98 | return -EINVAL; |
| 99 | |
| 100 | mfrac = ((u64)fvcoiic % 114285000LL) * 262144LL |
| 101 | / 114285000LL; |
| 102 | |
| 103 | *_mint = mint; |
| 104 | *_mfrac = mfrac; |
| 105 | *_n = n; |
| 106 | |
| 107 | return 0; |
| 108 | } |
| 109 | |
| 110 | static ulong ics8n3qv01_set_rate(struct clk *clk, ulong fout) |
| 111 | { |
| 112 | struct ics8n3qv01_priv *priv = dev_get_priv(clk->dev); |
| 113 | uint n, mint, mfrac; |
| 114 | uint fout_calc = 0; |
| 115 | u64 fout_prog; |
| 116 | long long off_ppm; |
| 117 | int res, i; |
| 118 | u8 reg[6]; |
| 119 | int tmp; |
| 120 | int addr[] = {0, 4, 8, 12, 18, 20}; |
| 121 | |
| 122 | priv->rate = fout; |
| 123 | |
| 124 | res = ics8n3qv01_get_fout_calc(clk->dev, 1, &fout_calc); |
| 125 | |
| 126 | if (res) { |
| 127 | debug("%s: Error during output frequency calculation.\n", |
| 128 | clk->dev->name); |
| 129 | return res; |
| 130 | } |
| 131 | |
| 132 | off_ppm = (fout_calc - ICS8N3QV01_F_DEFAULT_1) * 1000000 |
| 133 | / ICS8N3QV01_F_DEFAULT_1; |
| 134 | printf("%s: PLL is off by %lld ppm\n", clk->dev->name, off_ppm); |
| 135 | fout_prog = (u64)fout * (u64)fout_calc |
| 136 | / ICS8N3QV01_F_DEFAULT_1; |
| 137 | res = ics8n3qv01_calc_parameters(fout_prog, &mint, &mfrac, &n); |
| 138 | |
| 139 | if (res) { |
| 140 | debug("%s: Cannot determine mint parameter.\n", |
| 141 | clk->dev->name); |
| 142 | return res; |
| 143 | } |
| 144 | |
| 145 | /* Register 0 */ |
| 146 | tmp = dm_i2c_reg_read(clk->dev, 0) & 0xc0; |
| 147 | if (tmp < 0) |
| 148 | return tmp; |
| 149 | reg[0] = tmp | (mint & 0x1f) << 1; |
| 150 | reg[0] |= (mfrac >> 17) & 0x01; |
| 151 | |
| 152 | /* Register 4 */ |
| 153 | reg[1] = mfrac >> 9; |
| 154 | |
| 155 | /* Register 8 */ |
| 156 | reg[2] = mfrac >> 1; |
| 157 | |
| 158 | /* Register 12 */ |
| 159 | reg[3] = mfrac << 7; |
| 160 | reg[3] |= n & 0x7f; |
| 161 | |
| 162 | /* Register 18 */ |
| 163 | tmp = dm_i2c_reg_read(clk->dev, 18) & 0x03; |
| 164 | if (tmp < 0) |
| 165 | return tmp; |
| 166 | reg[4] = tmp | 0x20; |
| 167 | |
| 168 | /* Register 20 */ |
| 169 | tmp = dm_i2c_reg_read(clk->dev, 20) & 0x1f; |
| 170 | if (tmp < 0) |
| 171 | return tmp; |
| 172 | reg[5] = tmp | (mint & (1 << 5)); |
| 173 | |
| 174 | for (i = 0; i <= 5; ++i) { |
| 175 | res = dm_i2c_reg_write(clk->dev, addr[i], reg[i]); |
| 176 | if (res < 0) |
| 177 | return res; |
| 178 | } |
| 179 | |
| 180 | return 0; |
| 181 | } |
| 182 | |
Mario Six | f0bcbe6 | 2018-04-27 14:53:15 +0200 | [diff] [blame] | 183 | static ulong ics8n3qv01_get_rate(struct clk *clk) |
| 184 | { |
| 185 | struct ics8n3qv01_priv *priv = dev_get_priv(clk->dev); |
| 186 | |
| 187 | return priv->rate; |
| 188 | } |
| 189 | |
| 190 | static int ics8n3qv01_enable(struct clk *clk) |
| 191 | { |
| 192 | return 0; |
| 193 | } |
| 194 | |
| 195 | static int ics8n3qv01_disable(struct clk *clk) |
| 196 | { |
| 197 | return 0; |
| 198 | } |
| 199 | |
| 200 | static const struct clk_ops ics8n3qv01_ops = { |
Mario Six | f0bcbe6 | 2018-04-27 14:53:15 +0200 | [diff] [blame] | 201 | .get_rate = ics8n3qv01_get_rate, |
| 202 | .set_rate = ics8n3qv01_set_rate, |
| 203 | .enable = ics8n3qv01_enable, |
| 204 | .disable = ics8n3qv01_disable, |
| 205 | }; |
| 206 | |
| 207 | static const struct udevice_id ics8n3qv01_ids[] = { |
| 208 | { .compatible = "idt,ics8n3qv01" }, |
| 209 | { /* sentinel */ } |
| 210 | }; |
| 211 | |
| 212 | int ics8n3qv01_probe(struct udevice *dev) |
| 213 | { |
| 214 | return 0; |
| 215 | } |
| 216 | |
| 217 | U_BOOT_DRIVER(ics8n3qv01) = { |
| 218 | .name = "ics8n3qv01", |
| 219 | .id = UCLASS_CLK, |
| 220 | .ops = &ics8n3qv01_ops, |
| 221 | .of_match = ics8n3qv01_ids, |
| 222 | .probe = ics8n3qv01_probe, |
Simon Glass | 41575d8 | 2020-12-03 16:55:17 -0700 | [diff] [blame] | 223 | .priv_auto = sizeof(struct ics8n3qv01_priv), |
Mario Six | f0bcbe6 | 2018-04-27 14:53:15 +0200 | [diff] [blame] | 224 | }; |