|
1 | 1 | /* |
2 | | - * Copyright 2012-2022 Great Scott Gadgets <info@greatscottgadgets.com> |
| 2 | + * Copyright 2012-2026 Great Scott Gadgets <info@greatscottgadgets.com> |
3 | 3 | * Copyright 2012 Will Code <willcode4@gmail.com> |
4 | 4 | * Copyright 2014 Jared Boone <jared@sharebrained.com> |
5 | 5 | * |
|
29 | 29 | * pirate commands to do the same thing. |
30 | 30 | */ |
31 | 31 |
|
| 32 | +#include "max2837.h" |
| 33 | + |
| 34 | +#include <stdbool.h> |
32 | 35 | #include <stdint.h> |
33 | 36 | #include <string.h> |
34 | | -#include "max2837.h" |
| 37 | +#include "fixed_point.h" |
35 | 38 | #include "max2837_regs.def" // private register def macros |
36 | 39 | #include "selftest.h" |
37 | 40 |
|
| 41 | +#define MIN(x, y) ((x) < (y) ? (x) : (y)) |
| 42 | +#define MAX(x, y) ((x) > (y) ? (x) : (y)) |
| 43 | + |
38 | 44 | /* Default register values. */ |
39 | 45 | static const uint16_t max2837_regs_default[MAX2837_NUM_REGS] = { |
40 | 46 | 0x150, /* 0 */ |
@@ -228,60 +234,67 @@ void max2837_stop(max2837_driver_t* const drv) |
228 | 234 | max2837_set_mode(drv, MAX2837_MODE_SHUTDOWN); |
229 | 235 | } |
230 | 236 |
|
231 | | -void max2837_set_frequency(max2837_driver_t* const drv, uint32_t freq) |
| 237 | +/* Assume 40 MHz reference clock with R divider of 1. */ |
| 238 | +#define PFD_FREQ_HZ (40ULL * (1000ULL * 1000ULL)) |
| 239 | + |
| 240 | +#define MIN_FREQ FP_MHZ(2000) |
| 241 | +#define MAX_FREQ FP_MHZ(3000) |
| 242 | + |
| 243 | +fp_40_24_t max2837_set_frequency( |
| 244 | + max2837_driver_t* const drv, |
| 245 | + fp_40_24_t freq, |
| 246 | + bool program) |
232 | 247 | { |
233 | 248 | uint8_t band; |
234 | 249 | uint8_t lna_band; |
235 | | - uint32_t div_frac; |
236 | | - uint32_t div_int; |
237 | | - uint32_t div_rem; |
238 | | - uint32_t div_cmp; |
239 | | - int i; |
| 250 | + uint64_t div; |
| 251 | + |
| 252 | + freq = MIN(freq, MAX_FREQ); |
| 253 | + freq = MAX(freq, MIN_FREQ); |
240 | 254 |
|
241 | 255 | /* Select band. Allow tuning outside specified bands. */ |
242 | | - if (freq < 2400000000U) { |
| 256 | + if (freq < FP_MHZ(2400)) { |
243 | 257 | band = MAX2837_LOGEN_BSW_2_3; |
244 | 258 | lna_band = MAX2837_LNAband_2_4; |
245 | | - } else if (freq < 2500000000U) { |
| 259 | + } else if (freq < FP_MHZ(2500)) { |
246 | 260 | band = MAX2837_LOGEN_BSW_2_4; |
247 | 261 | lna_band = MAX2837_LNAband_2_4; |
248 | | - } else if (freq < 2600000000U) { |
| 262 | + } else if (freq < FP_MHZ(2600)) { |
249 | 263 | band = MAX2837_LOGEN_BSW_2_5; |
250 | 264 | lna_band = MAX2837_LNAband_2_6; |
251 | 265 | } else { |
252 | 266 | band = MAX2837_LOGEN_BSW_2_6; |
253 | 267 | lna_band = MAX2837_LNAband_2_6; |
254 | 268 | } |
255 | 269 |
|
256 | | - /* ASSUME 40MHz PLL. Ratio = F*(4/3)/40,000,000 = F/30,000,000 */ |
257 | | - freq += (30000000 >> 21); /* round to nearest frequency */ |
258 | | - div_int = freq / 30000000; |
259 | | - div_rem = freq % 30000000; |
260 | | - div_frac = 0; |
261 | | - div_cmp = 30000000; |
262 | | - for (i = 0; i < 20; i++) { |
263 | | - div_frac <<= 1; |
264 | | - div_rem <<= 1; |
265 | | - if (div_rem >= div_cmp) { |
266 | | - div_frac |= 0x1; |
267 | | - div_rem -= div_cmp; |
268 | | - } |
| 270 | + fp_40_24_t vco = (freq * 4) / 3; |
| 271 | + |
| 272 | + vco += ((PFD_FREQ_HZ * FP_ONE_HZ) >> 21); /* round to nearest frequency */ |
| 273 | + div = vco / PFD_FREQ_HZ; |
| 274 | + |
| 275 | + /* |
| 276 | + * Shift from 40.24 fixed-point to 44.20 to match 20-bit fractional |
| 277 | + * divider. |
| 278 | + */ |
| 279 | + div = div >> 4; |
| 280 | + |
| 281 | + if (program) { |
| 282 | + /* Band settings */ |
| 283 | + set_MAX2837_LOGEN_BSW(drv, band); |
| 284 | + set_MAX2837_LNAband(drv, lna_band); |
| 285 | + |
| 286 | + /* |
| 287 | + * Write order matters here, so commit INT and FRAC_HI before |
| 288 | + * committing FRAC_LO, which is the trigger for VCO auto-select. |
| 289 | + */ |
| 290 | + set_MAX2837_SYN_INT(drv, (div >> 20) & 0xff); |
| 291 | + set_MAX2837_SYN_FRAC_HI(drv, (div >> 10) & 0x3ff); |
| 292 | + max2837_regs_commit(drv); |
| 293 | + set_MAX2837_SYN_FRAC_LO(drv, div & 0x3ff); |
| 294 | + max2837_regs_commit(drv); |
269 | 295 | } |
270 | 296 |
|
271 | | - /* Band settings */ |
272 | | - set_MAX2837_LOGEN_BSW(drv, band); |
273 | | - set_MAX2837_LNAband(drv, lna_band); |
274 | | - |
275 | | - /* Write order matters here, so commit INT and FRAC_HI before |
276 | | - * committing FRAC_LO, which is the trigger for VCO |
277 | | - * auto-select. TODO - it's cleaner this way, but it would be |
278 | | - * faster to explicitly commit the registers explicitly so the |
279 | | - * dirty bits aren't scanned twice. */ |
280 | | - set_MAX2837_SYN_INT(drv, div_int); |
281 | | - set_MAX2837_SYN_FRAC_HI(drv, (div_frac >> 10) & 0x3ff); |
282 | | - max2837_regs_commit(drv); |
283 | | - set_MAX2837_SYN_FRAC_LO(drv, div_frac & 0x3ff); |
284 | | - max2837_regs_commit(drv); |
| 297 | + return ((PFD_FREQ_HZ * 3) / 4) * (div << 4); |
285 | 298 | } |
286 | 299 |
|
287 | 300 | typedef struct { |
|
0 commit comments