summaryrefslogtreecommitdiff
path: root/bsp/lpc5410x/Libraries/lpc_chip/chip_5410x/pll_5410x.c
blob: d99fd859166f71f66934215abfe1d760f66175c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
/*
 * @brief LPC5410X PLL driver
 *
 * @note
 * Copyright(C) NXP Semiconductors, 2014
 * All rights reserved.
 *
 * @par
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * LPC products.  This software is supplied "AS IS" without any warranties of
 * any kind, and NXP Semiconductors and its licenser disclaim any and
 * all warranties, express or implied, including all implied warranties of
 * merchantability, fitness for a particular purpose and non-infringement of
 * intellectual property rights.  NXP Semiconductors assumes no responsibility
 * or liability for the use of the software, conveys no license or rights under any
 * patent, copyright, mask work right, or any other intellectual property rights in
 * or to any products. NXP Semiconductors reserves the right to make changes
 * in the software without notification. NXP Semiconductors also makes no
 * representation or warranty that such application will be suitable for the
 * specified use without further testing or modification.
 *
 * @par
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, under NXP Semiconductors' and its
 * licensor's relevant copyrights in the software, without fee, provided that it
 * is used in conjunction with NXP Semiconductors microcontrollers.  This
 * copyright, permission, and disclaimer notice must appear in all copies of
 * this code.
 */

#include "chip.h"

/*****************************************************************************
 * Private types/enumerations/variables
 ****************************************************************************/

#define NVALMAX                     (0x100)
#define PVALMAX                     (0x20)
#define MVALMAX                     (0x8000)

/* SYS PLL related bit fields */
#define SYS_PLL_SELR(d)             (((d) & 0xf) << 0)		/*!< Bandwidth select R value */
#define SYS_PLL_SELI(d)             (((d) & 0x3f) << 4)		/*!< Bandwidth select I value */
#define SYS_PLL_SELP(d)             (((d) & 0x1f) << 10)	/*!< Bandwidth select P value */
#define SYS_PLL_BYPASS              (1 << 15)				/*!< Enable PLL bypass */
#define SYS_PLL_BYPASSCCODIV2       (1 << 16)				/*!< Enable bypass of extra divider by 2 */
#define SYS_PLL_UPLIMOFF            (1 << 17)				/*!< Enable spread spectrum/fractional mode */
#define SYS_PLL_BANDSEL             (1 << 18)				/*!< Enable MDEC control */
#define SYS_PLL_DIRECTI             (1 << 19)				/*!< PLL0 direct input enable */
#define SYS_PLL_DIRECTO             (1 << 20)				/*!< PLL0 direct output enable */

// #define FRAC_BITS_SELI			(8)		// For retaining fractions in divisions
#define PLL_SSCG0_MDEC_VAL_P    (0)			// MDEC is in bits  16 downto 0
#define PLL_SSCG0_MDEC_VAL_M    (0x1FFFFUL << PLL_SSCG0_MDEC_VAL_P)		// NDEC is in bits  9 downto 0
#define PLL_NDEC_VAL_P          (0)			// NDEC is in bits  9:0
#define PLL_NDEC_VAL_M          (0x3FFUL << PLL_NDEC_VAL_P)
#define PLL_PDEC_VAL_P          (0)			// PDEC is in bits 6:0
#define PLL_PDEC_VAL_M          (0x3FFUL << PLL_PDEC_VAL_P)

#define PLL_MIN_CCO_FREQ_MHZ    (75000000)
#define PLL_MAX_CCO_FREQ_MHZ    (150000000)
#define PLL_LOWER_IN_LIMIT      (4000)				/*!< Minimum PLL input rate */
#define PLL_MIN_IN_SSMODE       (2000000)
#define PLL_MAX_IN_SSMODE       (4000000)

// Middle of the range values for spread-spectrum
#define PLL_SSCG_MF_FREQ_VALUE                               4
#define PLL_SSCG_MC_COMP_VALUE                               2
#define PLL_SSCG_MR_DEPTH_VALUE                              4
#define PLL_SSCG_DITHER_VALUE                                0

// pll SYSPLLCTRL Bits
#define SYSCON_SYSPLLCTRL_SELR_P                                0
#define SYSCON_SYSPLLCTRL_SELR_M                                (0xFUL << SYSCON_SYSPLLCTRL_SELR_P)
#define SYSCON_SYSPLLCTRL_SELI_P                                4
#define SYSCON_SYSPLLCTRL_SELI_M                                (0x3FUL << SYSCON_SYSPLLCTRL_SELI_P)
#define SYSCON_SYSPLLCTRL_SELP_P                                10
#define SYSCON_SYSPLLCTRL_SELP_M                                (0x1FUL << SYSCON_SYSPLLCTRL_SELP_P)
#define SYSCON_SYSPLLCTRL_BYPASS_P                          15		// sys_pll150_ctrl
#define SYSCON_SYSPLLCTRL_BYPASS                                (1UL << SYSCON_SYSPLLCTRL_BYPASS_P)
#define SYSCON_SYSPLLCTRL_BYPASS_FBDIV2_P               16
#define SYSCON_SYSPLLCTRL_BYPASS_FBDIV2                 (1UL << SYSCON_SYSPLLCTRL_BYPASS_FBDIV2_P)
#define SYSCON_SYSPLLCTRL_UPLIMOFF_P                        17
#define SYSCON_SYSPLLCTRL_UPLIMOFF                          (1UL << SYSCON_SYSPLLCTRL_UPLIMOFF_P)
#define SYSCON_SYSPLLCTRL_BANDSEL_SSCGREG_N_P       18
#define SYSCON_SYSPLLCTRL_BANDSEL_SSCGREG_N         (1UL << SYSCON_SYSPLLCTRL_BANDSEL_SSCGREG_N_P)
#define SYSCON_SYSPLLCTRL_DIRECTI_P                         19
#define SYSCON_SYSPLLCTRL_DIRECTI                               (1UL << SYSCON_SYSPLLCTRL_DIRECTI_P)
#define SYSCON_SYSPLLCTRL_DIRECTO_P                         20
#define SYSCON_SYSPLLCTRL_DIRECTO                               (1UL << SYSCON_SYSPLLCTRL_DIRECTO_P)

#define SYSCON_SYSPLLSTAT_LOCK_P                                0
#define SYSCON_SYSPLLSTAT_LOCK                              (1UL << SYSCON_SYSPLLSTAT_LOCK_P)

#define PLL_CTRL_BYPASS_P                                                  15		// sys_pll150_ctrl
#define PLL_CTRL_BYPASS_FBDIV2_P                                           16
#define PLL_CTRL_UPLIMOFF_P                                                17
#define PLL_CTRL_BANDSEL_SSCGREG_N_P                                       18
#define PLL_CTRL_DIRECTI_P                                                 19
#define PLL_CTRL_DIRECTO_P                                                 20

#define PLL_CTRL_BYPASS                                                    (1 << PLL_CTRL_BYPASS_P)
#define PLL_CTRL_DIRECTI                                                   (1 << PLL_CTRL_DIRECTI_P)
#define PLL_CTRL_DIRECTO                                                   (1 << PLL_CTRL_DIRECTO_P)
#define PLL_CTRL_UPLIMOFF                                                  (1 << PLL_CTRL_UPLIMOFF_P)
#define PLL_CTRL_BANDSEL_SSCGREG_N                                         (1 << PLL_CTRL_BANDSEL_SSCGREG_N_P)
#define PLL_CTRL_BYPASS_FBDIV2                                             (1 << PLL_CTRL_BYPASS_FBDIV2_P)

// SSCG control[0]
// #define PLL_SSCG0_MDEC_VAL_P                                                0    // MDEC is in bits  16 downto 0
#define PLL_SSCG0_MREQ_P                                                   17
#define PLL_SSCG0_SEL_EXT_SSCG_N_P                                         18
#define PLL_SSCG0_SEL_EXT_SSCG_N                                           (1 << PLL_SSCG0_SEL_EXT_SSCG_N_P)
#define PLL_SSCG0_MREQ                                                     (1 << PLL_SSCG0_MREQ_P)

// SSCG control[1]
#define PLL_SSCG1_MD_REQ_P                                                 19
#define PLL_SSCG1_MOD_PD_SSCGCLK_N_P                                       28
#define PLL_SSCG1_DITHER_P                                                 29
#define PLL_SSCG1_MOD_PD_SSCGCLK_N                                         (1 << PLL_SSCG1_MOD_PD_SSCGCLK_N_P)
#define PLL_SSCG1_DITHER                                                   (1 << PLL_SSCG1_DITHER_P)
#define PLL_SSCG1_MD_REQ                                                   (1 << PLL_SSCG1_MD_REQ_P)

// PLL NDEC reg
#define PLL_NDEC_VAL_SET(value)                     (((unsigned long) (value) << PLL_NDEC_VAL_P) & PLL_NDEC_VAL_M)
#define PLL_NDEC_NREQ_P                                     10
#define PLL_NDEC_NREQ                                           (1 << PLL_NDEC_NREQ_P)

// PLL PDEC reg
#define PLL_PDEC_VAL_SET(value)                     (((unsigned long) (value) << PLL_PDEC_VAL_P) & PLL_PDEC_VAL_M)
#define PLL_PDEC_PREQ_P                                     7
#define PLL_PDEC_PREQ                                           (1 << PLL_PDEC_PREQ_P)

// SSCG control[0]
#define PLL_SSCG0_MDEC_VAL_SET(value)        (((unsigned long) (value) << PLL_SSCG0_MDEC_VAL_P) & PLL_SSCG0_MDEC_VAL_M)
#define PLL_SSCG0_MREQ_P                     17
#define PLL_SSCG0_MREQ                       (1 << PLL_SSCG0_MREQ_P)
#define PLL_SSCG0_SEL_EXT_SSCG_N_P           18
#define PLL_SSCG0_SEL_EXT_SSCG_N             (1 << PLL_SSCG0_SEL_EXT_SSCG_N_P)

// SSCG control[1]
#define PLL_SSCG1_MD_FRACT_P                                        0
#define PLL_SSCG1_MD_INT_P                                          11
#define PLL_SSCG1_MF_P                                              20
#define PLL_SSCG1_MC_P                                              26
#define PLL_SSCG1_MR_P                                              23

#define PLL_SSCG1_MD_FRACT_M                                        (0x7FFUL << PLL_SSCG1_MD_FRACT_P)
#define PLL_SSCG1_MD_INT_M                                          (0xFFUL << PLL_SSCG1_MD_INT_P)
#define PLL_SSCG1_MF_M                                              (0x7UL << PLL_SSCG1_MF_P)
#define PLL_SSCG1_MC_M                                              (0x3UL << PLL_SSCG1_MC_P)
#define PLL_SSCG1_MR_M                                              (0x7UL << PLL_SSCG1_MR_P)

#define PLL_SSCG1_MD_FRACT_SET(value)                               (((unsigned long) (value) << \
																	  PLL_SSCG1_MD_FRACT_P) & PLL_SSCG1_MD_FRACT_M)
#define PLL_SSCG1_MD_INT_SET(value)                                 (((unsigned long) (value) << \
																	  PLL_SSCG1_MD_INT_P)   & PLL_SSCG1_MD_INT_M)
// #define PLL_SSCG1_MF_SET(value)                                     (((unsigned long) (value) << \
// //																	  PLL_SSCG1_MF_P)       & PLL_SSCG1_MF_M)
// #define PLL_SSCG1_MC_SET(value)                                     (((unsigned long) (value) << \
// //																	  PLL_SSCG1_MC_P)       & PLL_SSCG1_MC_M)
// #define PLL_SSCG1_MR_SET(value)                                     (((unsigned long) (value) << \
// //																	  PLL_SSCG1_MR_P)       & PLL_SSCG1_MR_M)

// Middle of the range values for spread-spectrum
#define PLL0_SSCG_MF_FREQ_VALUE     4
#define PLL0_SSCG_MC_COMP_VALUE     2
#define PLL0_SSCG_MR_DEPTH_VALUE    4
#define PLL0_SSCG_DITHER_VALUE      0

#define PLL_MAX_N_DIV       0x100

/* Saved value of PLL output rate, computed whenever needed to save run-time
   computation on each call to retrive the PLL rate. */
static uint32_t curPllRate;

/*****************************************************************************
 * Public types/enumerations/variables
 ****************************************************************************/

/*****************************************************************************
 * Private functions
 ****************************************************************************/

/* Find encoded NDEC value for raw N value, max N = NVALMAX */
static uint32_t pllEncodeN(uint32_t N)
{
	uint32_t x, i;

	/* Find NDec */
	switch (N) {
	case 0:
		x = 0xFFF;
		break;

	case 1:
		x = 0x302;
		break;

	case 2:
		x = 0x202;
		break;

	default:
		x = 0x080;
		for (i = N; i <= NVALMAX; i++) {
			x = (((x ^ (x >> 2) ^ (x >> 3) ^ (x >> 4)) & 1) << 7) | ((x >> 1) & 0x7F);
		}
		break;
	}

	return x & (PLL_NDEC_VAL_M >> PLL_NDEC_VAL_P);
}

/* Find decoded N value for raw NDEC value */
static uint32_t pllDecodeN(uint32_t NDEC)
{
	uint32_t n, x, i;

	/* Find NDec */
	switch (NDEC) {
	case 0xFFF:
		n = 0;
		break;

	case 0x302:
		n = 1;
		break;

	case 0x202:
		n = 2;
		break;

	default:
		x = 0x080;
		n = 0xFFFFFFFF;
		for (i = NVALMAX; ((i >= 3) && (n == 0xFFFFFFFF)); i--) {
			x = (((x ^ (x >> 2) ^ (x >> 3) ^ (x >> 4)) & 1) << 7) | ((x >> 1) & 0x7F);
			if ((x & (PLL_NDEC_VAL_M >> PLL_NDEC_VAL_P)) == NDEC) {
				/* Decoded value of NDEC */
				n = i;
			}
		}
		break;
	}

	return n;
}

/* Find encoded PDEC value for raw P value, max P = PVALMAX */
static uint32_t pllEncodeP(uint32_t P)
{
	uint32_t x, i;

	/* Find PDec */
	switch (P) {
	case 0:
		x = 0xFF;
		break;

	case 1:
		x = 0x62;
		break;

	case 2:
		x = 0x42;
		break;

	default:
		x = 0x10;
		for (i = P; i <= PVALMAX; i++) {
			x = (((x ^ (x >> 2)) & 1) << 4) | ((x >> 1) & 0xF);
		}
		break;
	}

	return x & (PLL_PDEC_VAL_M >> PLL_PDEC_VAL_P);
}

/* Find decoded P value for raw PDEC value */
static uint32_t pllDecodeP(uint32_t PDEC)
{
	uint32_t p, x, i;

	/* Find PDec */
	switch (PDEC) {
	case 0xFF:
		p = 0;
		break;

	case 0x62:
		p = 1;
		break;

	case 0x42:
		p = 2;
		break;

	default:
		x = 0x10;
		p = 0xFFFFFFFF;
		for (i = PVALMAX; ((i >= 3) && (p == 0xFFFFFFFF)); i--) {
			x = (((x ^ (x >> 2)) & 1) << 4) | ((x >> 1) & 0xF);
			if ((x & (PLL_PDEC_VAL_M >> PLL_PDEC_VAL_P)) == PDEC) {
				/* Decoded value of PDEC */
				p = i;
			}
		}
		break;
	}

	return p;
}

/* Find encoded MDEC value for raw M value, max M = MVALMAX */
static uint32_t pllEncodeM(uint32_t M)
{
	uint32_t i, x;

	/* Find MDec */
	switch (M) {
	case 0:
		x = 0xFFFFF;
		break;

	case 1:
		x = 0x18003;
		break;

	case 2:
		x = 0x10003;
		break;

	default:
		x = 0x04000;
		for (i = M; i <= MVALMAX; i++) {
			x = (((x ^ (x >> 1)) & 1) << 14) | ((x >> 1) & 0x3FFF);
		}
		break;
	}

	return x & (PLL_SSCG0_MDEC_VAL_M >> PLL_SSCG0_MDEC_VAL_P);
}

/* Find decoded M value for raw MDEC value */
static uint32_t pllDecodeM(uint32_t MDEC)
{
	uint32_t m, i, x;

	/* Find MDec */
	switch (MDEC) {
	case 0xFFFFF:
		m = 0;
		break;

	case 0x18003:
		m = 1;
		break;

	case 0x10003:
		m = 2;
		break;

	default:
		x = 0x04000;
		m = 0xFFFFFFFF;
		for (i = MVALMAX; ((i >= 3) && (m == 0xFFFFFFFF)); i--) {
			x = (((x ^ (x >> 1)) & 1) << 14) | ((x >> 1) & 0x3FFF);
			if ((x & (PLL_SSCG0_MDEC_VAL_M >> PLL_SSCG0_MDEC_VAL_P)) == MDEC) {
				/* Decoded value of MDEC */
				m = i;
			}
		}
		break;
	}

	return m;
}

/* Find SELP, SELI, and SELR values for raw M value, max M = MVALMAX */
static void pllFindSel(uint32_t M, bool bypassFBDIV2, uint32_t *pSelP, uint32_t *pSelI, uint32_t *pSelR)
{
	/* Bypass divider? */
	if (bypassFBDIV2) {
		M = M / 2;
	}

	/* bandwidth: compute selP from Multiplier */
	if (M < 60) {
		*pSelP = (M >> 1) + 1;
	}
	else {
		*pSelP = PVALMAX - 1;
	}

	/* bandwidth: compute selI from Multiplier */
	if (M > 16384) {
		*pSelI = 1;
	}
	else if (M > 8192) {
		*pSelI = 2;
	}
	else if (M > 2048) {
		*pSelI = 4;
	}
	else if (M >= 501) {
		*pSelI = 8;
	}
	else if (M >= 60) {
		*pSelI = 4 * (1024 / (M + 9));
	}
	else {
		*pSelI = (M & 0x3C) + 4;
	}

	if (*pSelI > (SYSCON_SYSPLLCTRL_SELI_M >> SYSCON_SYSPLLCTRL_SELI_P)) {
		*pSelI = (SYSCON_SYSPLLCTRL_SELI_M >> SYSCON_SYSPLLCTRL_SELI_P);
	}

	*pSelR = 0;
}

/* Get predivider (N) from PLL NDEC setting */
uint32_t findPllPreDiv(uint32_t ctrlReg, uint32_t nDecReg)
{
	uint32_t preDiv = 1;

	/* Direct input is not used? */
	if ((ctrlReg & SYSCON_SYSPLLCTRL_DIRECTI) == 0) {
		/* Decode NDEC value to get (N) pre divider */
		preDiv = pllDecodeN(nDecReg & 0x3FF);
		if (preDiv == 0) {
			preDiv = 1;
		}
	}

	/* Adjusted by 1, directi is used to bypass */
	return preDiv;
}

/* Get postdivider (P) from PLL PDEC setting */
uint32_t findPllPostDiv(uint32_t ctrlReg, uint32_t pDecReg)
{
	uint32_t postDiv = 1;

	/* Direct input is not used? */
	if ((ctrlReg & SYS_PLL_DIRECTO) == 0) {
		/* Decode PDEC value to get (P) post divider */
		postDiv = 2 * pllDecodeP(pDecReg & 0x7F);
		if (postDiv == 0) {
			postDiv = 2;
		}
	}

	/* Adjusted by 1, directo is used to bypass */
	return postDiv;
}

/* Get multiplier (M) from PLL MDEC and BYPASS_FBDIV2 settings */
uint32_t findPllMMult(uint32_t ctrlReg, uint32_t mDecReg)
{
	uint32_t mMult = 1;

	/* Decode MDEC value to get (M) multiplier */
	mMult = pllDecodeM(mDecReg & 0x1FFFF);

	/* Extra divided by 2 needed? */
	if ((ctrlReg & SYSCON_SYSPLLCTRL_BYPASS_FBDIV2) == 0) {
		mMult = mMult >> 1;
	}

	if (mMult == 0) {
		mMult = 1;
	}

	return mMult;
}

static uint32_t FindGreatestCommonDivisor(uint32_t m, uint32_t n)
{
	uint32_t tmp;

	while (n != 0) {
		tmp = n;
		n = m % n;
		m = tmp;
	}

	return m;
}

/* Set PLL output based on desired output rate */
static PLL_ERROR_T Chip_Clock_GetPllConfig(uint32_t finHz, uint32_t foutHz, PLL_SETUP_T *pSetup,
										   bool useFeedbackDiv2, bool useSS)
{
	uint32_t nDivOutHz, fccoHz, multFccoDiv;
	uint32_t pllPreDivider, pllMultiplier, pllBypassFBDIV2, pllPostDivider;
	uint32_t pllDirectInput, pllDirectOutput;
	uint32_t pllSelP, pllSelI, pllSelR, bandsel, uplimoff;

	/* Baseline parameters (no input or output dividers) */
	pllPreDivider = 1;	/* 1 implies pre-divider will be disabled */
	pllPostDivider = 0;	/* 0 implies post-divider will be disabled */
	pllDirectOutput = 1;
	if (useFeedbackDiv2) {
		/* Using feedback divider for M, so disable bypass */
		pllBypassFBDIV2 = 0;
	}
	else {
		pllBypassFBDIV2 = 1;
	}
	multFccoDiv = (2 - pllBypassFBDIV2);

	/* Verify output rate parameter */
	if (foutHz > PLL_MAX_CCO_FREQ_MHZ) {
		/* Maximum PLL output with post divider=1 cannot go above this frequency */
		return PLL_ERROR_OUTPUT_TOO_HIGH;
	}
	if (foutHz < (PLL_MIN_CCO_FREQ_MHZ / (PVALMAX << 1))) {
		/* Minmum PLL output with maximum post divider cannot go below this frequency */
		return PLL_ERROR_OUTPUT_TOO_LOW;
	}

	/* If using SS mode, input clock needs to be between 2MHz and 4MHz */
	if (useSS) {
		/* Verify input rate parameter */
		if (finHz < PLL_MIN_IN_SSMODE) {
			/* Input clock into the PLL cannot be lower than this */
			return PLL_ERROR_INPUT_TOO_LOW;
		}

		/* PLL input in SS mode must be under 4MHz */
		pllPreDivider = finHz / ((PLL_MIN_IN_SSMODE + PLL_MAX_IN_SSMODE) / 2);
		if (pllPreDivider > NVALMAX) {
			return PLL_ERROR_INPUT_TOO_HIGH;
		}
	}
	else {
		/* Verify input rate parameter */
		if (finHz < PLL_LOWER_IN_LIMIT) {
			/* Input clock into the PLL cannot be lower than this */
			return PLL_ERROR_INPUT_TOO_LOW;
		}
	}

	/* Find the optimal CCO frequency for the output and input that
	   will keep it inside the PLL CCO range. This may require
	   tweaking the post-divider for the PLL. */
	fccoHz = foutHz;
	while (fccoHz < PLL_MIN_CCO_FREQ_MHZ) {
		/* CCO output is less than minimum CCO range, so the CCO output
		   needs to be bumped up and the post-divider is used to bring
		   the PLL output back down. */
		pllPostDivider++;
		if (pllPostDivider > PVALMAX) {
			return PLL_ERROR_OUTSIDE_INTLIMIT;
		}

		/* Target CCO goes up, PLL output goes down */
		fccoHz = foutHz * (pllPostDivider * 2);
		pllDirectOutput = 0;
	}

	/* Determine if a pre-divider is needed to get the best frequency */
	if ((finHz > PLL_LOWER_IN_LIMIT) && (fccoHz >= finHz) && (useSS == false)) {
		uint32_t a = FindGreatestCommonDivisor(fccoHz, (multFccoDiv * finHz));

		if (a > 20000) {
			a = (multFccoDiv * finHz) / a;
			if ((a != 0) && (a < PLL_MAX_N_DIV)) {
				pllPreDivider = a;
			}
		}
	}

	/* Bypass pre-divider hardware if pre-divider is 1 */
	if (pllPreDivider > 1) {
		pllDirectInput = 0;
	}
	else {
		pllDirectInput = 1;
	}

	/* Determine PLL multipler */
	nDivOutHz = (finHz / pllPreDivider);
	pllMultiplier = (fccoHz / nDivOutHz) / multFccoDiv;

	/* Find optimal values for filter */
	if (useSS == false) {
		/* Will bumping up M by 1 get us closer to the desired CCO frequency? */
		if ((nDivOutHz * ((multFccoDiv * pllMultiplier * 2) + 1)) < (fccoHz * 2)) {
			pllMultiplier++;
		}

		/* Setup filtering */
		pllFindSel(pllMultiplier, pllBypassFBDIV2, &pllSelP, &pllSelI, &pllSelR);
		bandsel = 1;
		uplimoff = 0;

		/* Get encoded value for M (mult) and use manual filter, disable SS mode */
		pSetup->SYSPLLSSCTRL[0] = (PLL_SSCG0_MDEC_VAL_SET(pllEncodeM(pllMultiplier)) |
								   (1 << PLL_SSCG0_SEL_EXT_SSCG_N_P));

		/* Power down SSC, not used */
		pSetup->SYSPLLSSCTRL[1] = PLL_SSCG1_MOD_PD_SSCGCLK_N;
	}
	else {
		uint64_t fc;

		/* Filtering will be handled by SSC */
		pllSelR = pllSelI = pllSelP = 0;
		bandsel = 0;
		uplimoff = 1;

		/* The PLL multiplier will get very close and slightly under the
		   desired target frequency. A small fractional component can be
		   added to fine tune the frequency upwards to the target. */
		fc = ((uint64_t) (fccoHz % (multFccoDiv * nDivOutHz)) << 11) / (multFccoDiv * nDivOutHz);

		/* MDEC set by SSC */
		pSetup->SYSPLLSSCTRL[0] = 0;

		/* Set multiplier */
		pSetup->SYSPLLSSCTRL[1] = PLL_SSCG1_MD_INT_SET(pllMultiplier) |
								  PLL_SSCG1_MD_FRACT_SET((uint32_t) fc);
	}

	/* Get encoded values for N (prediv) and P (postdiv) */
	pSetup->SYSPLLNDEC = PLL_NDEC_VAL_SET(pllEncodeN(pllPreDivider));
	pSetup->SYSPLLPDEC = PLL_PDEC_VAL_SET(pllEncodeP(pllPostDivider));

	/* PLL control */
	pSetup->SYSPLLCTRL =
		(pllSelR << SYSCON_SYSPLLCTRL_SELR_P) |					/* Filter coefficient */
		(pllSelI << SYSCON_SYSPLLCTRL_SELI_P) |					/* Filter coefficient */
		(pllSelP << SYSCON_SYSPLLCTRL_SELP_P) |					/* Filter coefficient */
		(0 << SYSCON_SYSPLLCTRL_BYPASS_P) |						/* PLL bypass mode disabled */
		(pllBypassFBDIV2 << SYSCON_SYSPLLCTRL_BYPASS_FBDIV2_P) |	/* Extra M / 2 divider? */
		(uplimoff << SYSCON_SYSPLLCTRL_UPLIMOFF_P) |			/* SS/fractional mode disabled */
		(bandsel << SYSCON_SYSPLLCTRL_BANDSEL_SSCGREG_N_P) |	/* Manual bandwidth selection enabled */
		(pllDirectInput << SYSCON_SYSPLLCTRL_DIRECTI_P) |		/* Bypass pre-divider? */
		(pllDirectOutput << SYSCON_SYSPLLCTRL_DIRECTO_P);		/* Bypass post-divider? */

	return PLL_ERROR_SUCCESS;
}

/* Update local PLL rate variable */
static void Chip_Clock_GetSystemPLLOutFromSetupUpdate(PLL_SETUP_T *pSetup)
{
	curPllRate = Chip_Clock_GetSystemPLLOutFromSetup(pSetup);
}

/*****************************************************************************
 * Public functions
 ****************************************************************************/

/* Return System PLL input clock rate */
uint32_t Chip_Clock_GetSystemPLLInClockRate(void)
{
	uint32_t clkRate = 0;

	switch ((CHIP_SYSCON_PLLCLKSRC_T) (LPC_SYSCON->SYSPLLCLKSEL & 0x3)) {
	case SYSCON_PLLCLKSRC_IRC:
		clkRate = Chip_Clock_GetIntOscRate();
		break;

	case SYSCON_PLLCLKSRC_CLKIN:
		clkRate = Chip_Clock_GetExtClockInRate();
		break;

	case SYSCON_PLLCLKSRC_WDTOSC:
		clkRate = Chip_Clock_GetWDTOSCRate();
		break;

	case SYSCON_PLLCLKSRC_RTC:
		clkRate = Chip_Clock_GetRTCOscRate();
		break;
	}

	return clkRate;
}

/* Return System PLL output clock rate from setup structure */
uint32_t Chip_Clock_GetSystemPLLOutFromSetup(PLL_SETUP_T *pSetup)
{
	uint32_t prediv, postdiv, mMult, inPllRate;
	uint64_t workRate;

	inPllRate = Chip_Clock_GetSystemPLLInClockRate();
	if ((pSetup->SYSPLLCTRL & SYSCON_SYSPLLCTRL_BYPASS_P) == 0) {
		/* PLL is not in bypass mode, get pre-divider, post-divider, and M divider */
		prediv = findPllPreDiv(pSetup->SYSPLLCTRL, pSetup->SYSPLLNDEC);
		postdiv = findPllPostDiv(pSetup->SYSPLLCTRL, pSetup->SYSPLLPDEC);

		/* Adjust input clock */
		inPllRate = inPllRate / prediv;

		/* If using the SS, use the multiplier */
		if (pSetup->SYSPLLSSCTRL[1] & PLL_SSCG1_MOD_PD_SSCGCLK_N) {
			/* MDEC used for rate */
			mMult = findPllMMult(pSetup->SYSPLLCTRL, pSetup->SYSPLLSSCTRL[0]);
			workRate = (uint64_t) inPllRate * (uint64_t) mMult;
		}
		else {
			uint64_t fract;

			/* SS multipler used for rate */
			mMult = (pSetup->SYSPLLSSCTRL[1] & PLL_SSCG1_MD_INT_M) >> PLL_SSCG1_MD_INT_P;
			workRate = (uint64_t) inPllRate * (uint64_t) mMult;

			/* Adjust by fractional */
			fract = (uint64_t) (pSetup->SYSPLLSSCTRL[1] & PLL_SSCG1_MD_FRACT_M) >> PLL_SSCG1_MD_FRACT_P;
			workRate = workRate + ((inPllRate * fract) / 0x7FF);
		}

		workRate = workRate / ((uint64_t) postdiv);
	}
	else {
		/* In bypass mode */
		workRate = (uint64_t) inPllRate;
	}

	return (uint32_t) workRate;
}

/* Return System PLL output clock rate */
uint32_t Chip_Clock_GetSystemPLLOutClockRate(bool recompute)
{
	PLL_SETUP_T Setup;
	uint32_t rate;

	if ((recompute) || (curPllRate == 0)) {
		Setup.SYSPLLCTRL = LPC_SYSCON->SYSPLLCTRL;
		Setup.SYSPLLNDEC = LPC_SYSCON->SYSPLLNDEC;
		Setup.SYSPLLPDEC = LPC_SYSCON->SYSPLLPDEC;
		Setup.SYSPLLSSCTRL[0] = LPC_SYSCON->SYSPLLSSCTRL[0];
		Setup.SYSPLLSSCTRL[1] = LPC_SYSCON->SYSPLLSSCTRL[1];

		Chip_Clock_GetSystemPLLOutFromSetupUpdate(&Setup);
	}

	rate = curPllRate;

	return rate;
}

/* Enables and disables PLL bypass mode */
void Chip_Clock_SetBypassPLL(bool bypass)
{
	if (bypass) {
		LPC_SYSCON->SYSPLLCTRL |= SYSCON_SYSPLLCTRL_BYPASS_P;
	}
	else {
		LPC_SYSCON->SYSPLLCTRL &= ~SYSCON_SYSPLLCTRL_BYPASS_P;
	}
}

/* Set PLL output based on the passed PLL setup data */
PLL_ERROR_T Chip_Clock_SetupPLLData(PLL_CONFIG_T *pControl, PLL_SETUP_T *pSetup)
{
	uint32_t inRate;
	bool useSS = (bool) ((pControl->flags & PLL_CONFIGFLAG_FORCENOFRACT) == 0);
	PLL_ERROR_T pllError;

	/* Determine input rate for the PLL */
	if ((pControl->flags & PLL_CONFIGFLAG_USEINRATE) != 0) {
		inRate = pControl->InputRate;
	}
	else {
		inRate = Chip_Clock_GetSystemPLLInClockRate();
	}

	/* PLL flag options */
	pllError = Chip_Clock_GetPllConfig(inRate, pControl->desiredRate, pSetup, false, useSS);
	if ((useSS) && (pllError == PLL_ERROR_SUCCESS)) {
		/* If using SS mode, then some tweaks are made to the generated setup */
		pSetup->SYSPLLSSCTRL[1] |= (uint32_t) pControl->ss_mf | (uint32_t) pControl->ss_mr |
								   (uint32_t) pControl->ss_mc;
		if (pControl->mfDither) {
			pSetup->SYSPLLSSCTRL[1] |= PLL_SSCG1_DITHER;
		}
	}

	return pllError;
}

/* Set PLL output from PLL setup structure */
PLL_ERROR_T Chip_Clock_SetupSystemPLLPrec(PLL_SETUP_T *pSetup)
{
	/* Power off PLL during setup changes */
	Chip_SYSCON_PowerDown(SYSCON_PDRUNCFG_PD_SYS_PLL);

	/* Write PLL setup data */
	LPC_SYSCON->SYSPLLCTRL = pSetup->SYSPLLCTRL;
	LPC_SYSCON->SYSPLLNDEC = pSetup->SYSPLLNDEC;
	LPC_SYSCON->SYSPLLNDEC = pSetup->SYSPLLNDEC | PLL_NDEC_NREQ;/* latch */
	LPC_SYSCON->SYSPLLPDEC = pSetup->SYSPLLPDEC;
	LPC_SYSCON->SYSPLLPDEC = pSetup->SYSPLLPDEC | PLL_PDEC_PREQ;/* latch */
	LPC_SYSCON->SYSPLLSSCTRL[0] = pSetup->SYSPLLSSCTRL[0];
	LPC_SYSCON->SYSPLLSSCTRL[0] = pSetup->SYSPLLSSCTRL[0] | PLL_SSCG0_MREQ;	/* latch */
	LPC_SYSCON->SYSPLLSSCTRL[1] = pSetup->SYSPLLSSCTRL[1];
	LPC_SYSCON->SYSPLLSSCTRL[1] = pSetup->SYSPLLSSCTRL[1] | PLL_SSCG1_MD_REQ;	/* latch */

	/* Flags for lock or power on */
	if ((pSetup->flags & (PLL_SETUPFLAG_POWERUP | PLL_SETUPFLAG_WAITLOCK)) != 0) {
		Chip_SYSCON_PowerUp(SYSCON_PDRUNCFG_PD_SYS_PLL);
	}
	if ((pSetup->flags & PLL_SETUPFLAG_WAITLOCK) != 0) {
		while (Chip_Clock_IsSystemPLLLocked() == false) {}
	}

	/* Update current programmed PLL rate var */
	Chip_Clock_GetSystemPLLOutFromSetupUpdate(pSetup);

	/* System voltage adjustment, occurs prior to setting main system clock */
	if ((pSetup->flags & PLL_SETUPFLAG_ADGVOLT) != 0) {
		Chip_POWER_SetVoltage(POWER_LOW_POWER_MODE, curPllRate);
	}

	return PLL_ERROR_SUCCESS;
}

/* Set System PLL clock based on the input frequency and multiplier */
void Chip_Clock_SetupSystemPLL(uint32_t multiply_by, uint32_t input_freq)
{
	uint32_t cco_freq = input_freq * multiply_by;
	uint32_t pdec = 1;
	uint32_t selr;
	uint32_t seli;
	uint32_t selp;
	uint32_t mdec, ndec;

	uint32_t directo = SYS_PLL_DIRECTO;

	while (cco_freq < 75000000) {
		multiply_by <<= 1;	/* double value in each iteration */
		pdec <<= 1;			/* correspondingly double pdec to cancel effect of double msel */
		cco_freq = input_freq * multiply_by;
	}
	selr = 0;
	seli = (multiply_by & 0x3c) + 4;
	selp = (multiply_by >> 1) + 1;

	if (pdec > 1) {
		directo = 0;	/* use post divider */
		pdec = pdec / 2;	/* Account for minus 1 encoding */
		/* Translate P value */
		pdec = (pdec == 1)  ? 0x62 :	/* 1  * 2 */
			   (pdec == 2)  ? 0x42 :	/* 2  * 2 */
			   (pdec == 4)  ? 0x02 :	/* 4  * 2 */
			   (pdec == 8)  ? 0x0b :	/* 8  * 2 */
			   (pdec == 16) ? 0x11 :	/* 16 * 2 */
			   (pdec == 32) ? 0x08 : 0x08;	/* 32 * 2 */
	}

	/* Only support values of 2 to 16 (to keep driver simple) */
	mdec = 0x7fff >> (16 - (multiply_by - 1));
	ndec = 0x202;	/* pre divide by 2 (hardcoded) */

	LPC_SYSCON->SYSPLLCTRL = SYS_PLL_BANDSEL | directo | (selr << SYSCON_SYSPLLCTRL_SELR_P) |
							 (seli << SYSCON_SYSPLLCTRL_SELI_P) | (selp << SYSCON_SYSPLLCTRL_SELP_P);
	LPC_SYSCON->SYSPLLPDEC = pdec | (1 << 7);	/* set Pdec value and assert preq */
	LPC_SYSCON->SYSPLLNDEC = ndec | (1 << 10);	/* set Pdec value and assert preq */
	LPC_SYSCON->SYSPLLSSCTRL[0] = (1 << 18) | (1 << 17) | mdec;	/* select non sscg MDEC value, assert mreq and select mdec value */
}