File Coverage

blib/lib/Device/Chip/Si5351.pm
Criterion Covered Total %
statement 195 201 97.0
branch 23 46 50.0
condition 14 39 35.9
subroutine 24 25 96.0
pod 11 14 78.5
total 267 325 82.1


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2022 -- leonerd@leonerd.org.uk
5              
6 7     7   590057 use v5.26;
  7         70  
7 7     7   464 use Object::Pad 0.57;
  7         8740  
  7         29  
8              
9             package Device::Chip::Si5351 0.01;
10             class Device::Chip::Si5351
11 7     7   3886 :isa(Device::Chip::Base::RegisteredI2C);
  7         32243  
  7         262  
12 7     7   1092 use Device::Chip::Base::RegisteredI2C 0.21;
  7         144  
  7         163  
13              
14 7     7   30 use Carp;
  7         12  
  7         645  
15 7     7   82 use Future::AsyncAwait 0.38;
  7         131  
  7         34  
16              
17 7     7   3215 use Data::Bitfield qw( bitfield enumfield boolfield intfield );
  7         12027  
  7         790  
18              
19 7     7   3185 use POSIX qw( floor ); # TODO: grab this from builtin::
  7         37805  
  7         33  
20              
21             # See also
22             # https://github.com/adafruit/Adafruit_Si5351_Library/blob/master/Adafruit_SI5351.cpp
23             # AN619 = https://www.silabs.com/documents/public/application-notes/AN619.pdf
24              
25             =encoding UTF-8
26              
27             =head1 NAME
28              
29             C - chip driver for F
30              
31             =head1 SYNOPSIS
32              
33             use Device::Chip::Si5351;
34             use Future::AsyncAwait;
35              
36             my $chip = Device::Chip::Si5351->new;
37             await $chip->mount( Device::Chip::Adapter::...->new );
38              
39             await $chip->init;
40              
41             await $chip->change_pll_config( "A",
42             SRC => "XTAL",
43             ratio => 24,
44             );
45              
46             await $chip->change_multisynth_config( 0,
47             SRC => "PLLA",
48             ratio => 50,
49             );
50              
51             await $chip->change_clk_config( 0,
52             SRC => "MSn",
53             PDN => 0,
54             OE => 1,
55             );
56              
57             await $chip->reset_plls;
58              
59             # CLK0 output will now be set to the crystal reference frequency
60             # multiplied by 24, divided by 50. Assuming a 25.000MHz reference
61             # crystal, the output will therefore be 12.000MHz.
62              
63             =head1 DESCRIPTION
64              
65             This L subclass provides specific communication to a F
66             F chip attached to a computer via an I²C adapter.
67              
68             The reader is presumed to be familiar with the general operation of this chip;
69             the documentation here will not attempt to explain or define chip-specific
70             concepts or features, only the use of this module to access them.
71              
72             =cut
73              
74             method I2C_options
75 6     6 0 2519 {
76             return (
77 6         33 addr => 0x60,
78             max_bitrate => 400E3,
79             );
80             }
81              
82             =head1 METHODS
83              
84             =cut
85              
86             # Silabs' AN619 gives names for the register fields but not actually the
87             # registers themselves. We've made up these names here
88              
89             # Many of these registers aren't used in our code (yet).
90             use constant {
91             # Register numbers are documented in decimal in the AN619 datasheet
92 7         27022 REG_STATUS => 0, # (RO)
93             REG_INTFLAGS => 1,
94             REG_INTMASK => 2,
95             REG_OEMASK => 3,
96             REG_OEBMASK => 9,
97             REG_PLLSOURCE => 15,
98              
99             # The 8 clock control registers
100             REG_CLKCTRL_BASE => 16,
101              
102             REG_CLK03DIS => 24,
103             REG_CLK47DIS => 25,
104              
105             # Multisynth NA+NB have a common structure
106             REG_MSNA_BASE => 26,
107             REG_MSNB_BASE => 34,
108              
109             # Multisynth0 to 5 have a common structure
110             REG_MSx_BASE => 42,
111              
112             # Multisynth 6 to 7 are special
113             REG_MS6_P1L => 90,
114             REG_MS7_P1L => 91,
115             REG_MS67_DIV => 92,
116              
117             # TODO: spread spectrum, VCXO
118              
119             # Phase offset - despite its silly name it is a property of the Multisynth
120             # unit, not the clock output
121             REG_CLKx_PHOFF => 165,
122              
123             REG_PLLRST => 177,
124              
125             REG_XTAL_CL => 183,
126              
127             REG_FANOUT => 187,
128 7     7   8876 };
  7         16  
129              
130             =head2 init
131              
132             await $chip->init;
133              
134             Performs initialisation setup on the chip as recommended by the datasheet:
135             disables all outputs and powers down all output drivers. After this,
136             individual outputs can be powered up and enabled again by
137             L.
138              
139             =cut
140              
141             # A copy of the initialise code from the Adafruit driver
142 0         0 async method init ()
  0         0  
143 0         0 {
144             # All outputs disabled (bits high)
145 0         0 await $self->cached_write_reg( REG_OEMASK, "\xFF" );
146              
147             # All output drivers powered down
148             await Future->needs_all(
149 0         0 map { $self->cached_write_reg( REG_CLKCTRL_BASE + $_, "\x80" ) } 0 .. 7
150             );
151 0     0 1 0 }
152              
153             =head2 read_status
154              
155             $status = await $chip->read_status;
156              
157             Reads and returns the chip status register, as a C reference with the
158             following keys:
159              
160             SYS_INIT => BOOL
161             LOL_A => BOOL
162             LOL_B => BOOL
163             LOS_CLKIN => BOOL
164             LOS_XTAL => BOOL
165             REVID => INT
166              
167             =cut
168              
169             bitfield { format => "bytes-LE" }, STATUS =>
170             SYS_INIT => boolfield( 7 ),
171             LOL_A => boolfield( 6 ),
172             LOL_B => boolfield( 5 ),
173             LOS_CLKIN => boolfield( 4 ),
174             LOS_XTAL => boolfield( 3 ),
175             REVID => intfield( 0, 2 ),
176             ;
177              
178 1         1 async method read_status ()
  1         2  
179 1         3 {
180 1         5 my $bytes = await $self->read_reg( REG_STATUS, 1 );
181              
182 1         6763 return { unpack_STATUS( $bytes ) };
183 1     1 1 182 }
184              
185             =head2 read_config
186              
187             $config = await $chip->read_config;
188              
189             Reads and returns the overall chip configuration, as a C reference with
190             the following keys:
191              
192             XTAL_CL => "6pF" | "8pF" | "10pF"
193             CLKIN_FANOUT => BOOL
194             XO_FANOUT => BOOL
195             MS_FANOUT => BOOL
196              
197             =cut
198              
199             # Rather than one giant monster "config" structure, we'll split up
200             # chip overall
201             # PLLA, PLLB
202             # Multisynth0 to Multisynth7
203             # CLK0 to CLK7 outputs
204              
205             bitfield { format => "bytes-LE" }, CONFIG =>
206             # REG_XTAL_CL
207             XTAL_CL => enumfield( 6, qw( . 6pF 8pF 10pF ) ),
208             # REG_FANOUT
209             CLKIN_FANOUT => boolfield( 15 ),
210             XO_FANOUT => boolfield( 14 ),
211             MS_FANOUT => boolfield( 12 ),
212             ;
213              
214 2         2 async method read_config ()
  2         3  
215 2         4 {
216 2         8 my $bytes = join "",
217             await $self->cached_read_reg( REG_XTAL_CL, 1 ),
218             await $self->cached_read_reg( REG_FANOUT, 1 );
219              
220 2         8552 return { unpack_CONFIG( $bytes ) };
221 2     2 1 274 }
222              
223             =head2 change_config
224              
225             await $chip->change_config( %changes );
226              
227             Writes changes to the overall chip configuration registers. Any fields not
228             specified will retain their current values.
229              
230             =cut
231              
232 1         2 async method change_config ( %changes )
  1         2  
  1         1  
233 1         3 {
234 1         2 my $config = await $self->read_config();
235              
236 1         83 $config->{$_} = $changes{$_} for keys %changes;
237              
238 1         6 my ( $xtal_cl, $fanout ) = unpack "(a1)*", pack_CONFIG( %$config );
239              
240 1         91 await $self->cached_write_reg( REG_XTAL_CL, $xtal_cl );
241 1         196 await $self->cached_write_reg( REG_FANOUT, $fanout );
242 1     1 1 2967 }
243              
244             =head2 read_pll_config
245              
246             $config = await $chip->read_pll_config( $pll )
247              
248             Reads and returns the PLL synthesizer configuration registers for the given
249             PLL unit (which should be C<"A"> or C<"B">), as a C reference with the
250             following keys:
251              
252             P1 => INT
253             P2 => INT
254             P3 => INT
255             SRC => "XTAL" | "CLKIN"
256              
257             Additionally, the following extra fields will be inferred from the basic
258             parameters, as a convenience:
259              
260             ratio_a => INT # integral part of ratio
261             ratio_b => INT # numerator of fractional part of ratio
262             ratio_c => INT # denominator of fractional part of ratio
263              
264             ratio => NUM # ratio expressed as a float
265              
266             =cut
267              
268             sub unpack_PARAMS ( $bytes )
269 17     17 0 25 {
  17         25  
  17         19  
270 17         85 my ( $p3m, $p3l, $p1h, $p1m, $p1l, $p23h, $p2m, $p2l ) = unpack "(C)8", $bytes;
271 17         32 $p1h &= 0x03;
272 17         28 my $p2h = ($p23h)&0x0F;
273 17         25 my $p3h = ($p23h>>4);
274              
275             return (
276 17         87 P1 => $p1l | ($p1m << 8) | ($p1h << 16),
277             P2 => $p2l | ($p2m << 8) | ($p2h << 16),
278             P3 => $p3l | ($p3m << 8) | ($p3h << 16),
279             );
280             }
281              
282             sub pack_PARAMS ( %params )
283 6     6 0 19 {
  6         14  
  6         7  
284 6         18 my ( $p1, $p2, $p3 ) = @params{qw( P1 P2 P3 )};
285 6 50       17 $p1 == ($p1 & 0x3FFFF) or croak "P1 out of range";
286 6 50       24 $p2 == ($p2 & 0xFFFFF) or croak "P2 out of range";
287 6 50       12 $p3 == ($p3 & 0xFFFFF) or croak "P3 out of range";
288              
289 6         40 return pack "C C C C C C C C",
290             ($p3>>8)&0xFF, ($p3)&0xFF, ($p1>>16), ($p1>>8)&0xFF,
291             ($p1)&0xFF, ($p3>>16)<<4|($p2>>16), ($p2>>8)&0xFF, ($p2)&0xFF;
292             }
293              
294             sub _infer_ratio ( $config )
295 17     17   23 {
  17         24  
  17         21  
296 17 100       43 if( $config->{P2} == 0 ) {
297 13         41 $config->{ratio_a} = ( $config->{P1} + 512 ) / 128;
298 13         22 $config->{ratio_b} = 0;
299 13         17 $config->{ratio_c} = 1;
300             }
301             else {
302 4         11 my $ratio_c = $config->{ratio_c} = $config->{P3};
303 4         9 my $P1_ = $config->{P1} + 512;
304 4         40 $config->{ratio_a} = floor( $P1_ / 128 );
305 4         15 my $floor = $P1_ - 128 * $config->{ratio_a};
306 4         19 $config->{ratio_b} = floor( ( $config->{P2} + $floor * $ratio_c ) / 128 );
307             }
308              
309 17         51 $config->{ratio} = $config->{ratio_a} + $config->{ratio_b} / $config->{ratio_c};
310             }
311              
312             sub _calc_param ( $config )
313 5     5   9 {
  5         7  
  5         8  
314             # These equations taken straight from AN691
315 5         9 my ( $ratio_a, $ratio_b, $ratio_c ) = @{$config}{qw( ratio_a ratio_b ratio_c )};
  5         12  
316              
317 5         40 my $floor = floor( 128 * $ratio_b / $ratio_c );
318 5         19 $config->{P1} = 128 * $ratio_a + $floor - 512;
319 5         14 $config->{P2} = 128 * $ratio_b - $ratio_c * $floor;
320 5         12 $config->{P3} = $ratio_c;
321             }
322              
323 5         10 async method read_pll_config ( $pll )
  5         9  
  5         9  
324 5         30 {
325 5 0       18 my $regbase = ( $pll eq "A" ) ? REG_MSNA_BASE :
    50          
326             ( $pll eq "B" ) ? REG_MSNB_BASE : croak "Invalid PLL choice '$pll'";
327              
328 5         26 my $bytes = await $self->cached_read_reg( $regbase, 8 );
329              
330 5         10134 my %config = unpack_PARAMS( $bytes );
331              
332 5         19 my $pllsrc = unpack "C", await $self->cached_read_reg( REG_PLLSOURCE, 1 );
333 5 50       2149 $pllsrc &= ( $pll eq "A" ) ? (1<<2) : (1<<3);
334              
335 5         17 $config{SRC} = (qw( XTAL CLKIN ))[ !!$pllsrc ];
336              
337 5         20 _infer_ratio \%config;
338              
339 5         38 return \%config;
340 5     5 1 14188 }
341              
342             =head2 change_pll_config
343              
344             await $chip->change_pll_config( $pll, %changes )
345              
346             Writes changes to the PLL synthesizer configuration registers for the given
347             PLL unit. Any fields not specified will retain their current values.
348              
349             As a convenience, the feedback division ratio can be supplied using the three
350             C parameters, rather than the raw C values.
351              
352             To set an integer ratio, this can alternatively be supplied directly by the
353             C parameter. This must be an integer, however. To avoid floating-point
354             inaccuracies in fractional ratios, the three C parameters must be
355             used if the ratio is not a simple integer.
356              
357             =cut
358              
359 2         4 async method change_pll_config ( $pll, %changes )
  2         5  
  2         7  
  2         3  
360 2         10 {
361 2 0       7 my $regbase = ( $pll eq "A" ) ? REG_MSNA_BASE :
    50          
362             ( $pll eq "B" ) ? REG_MSNB_BASE : croak "Invalid PLL choice '$pll'";
363              
364 2         9 my $config = await $self->read_pll_config( $pll );
365              
366 2 100       50 if( exists $changes{ratio} ) {
367 1         3 my $ratio = delete $changes{ratio};
368              
369 1 50 33     9 15 <= $ratio and $ratio < 91 or
370             croak "Cannot set PLL multiplier ratio to $ratio";
371 1 50       5 $ratio == int $ratio or
372             croak "Cannot use 'ratio' to set a non-integer ratio; please supply ratio_a, ratio_b, ratio_c individually";
373              
374 1         3 $changes{ratio_a} = $ratio;
375 1         2 $changes{ratio_b} = 0;
376 1         2 $changes{ratio_c} = 1;
377             }
378              
379 2 0 33     8 if( exists $changes{ratio_a} or exists $changes{ratio_b} or exists $changes{ratio_c} ) {
      0        
380 2         6 _calc_param( \%changes );
381             }
382              
383 2   33     14 exists $changes{$_} and $config->{$_} = $changes{$_} for qw( P1 P2 P3 );
384              
385 2         11 my $bytes = pack_PARAMS( %$config );
386              
387 2         21 await $self->cached_write_reg( $regbase, $bytes );
388              
389 2         7958 await $self->reset_plls;
390 2     2 1 5868 }
391              
392             =head2 reset_plls
393              
394             await $chip->reset_plls;
395              
396             Resets the PLLs. This method should be called at the end of configuration to
397             reset the PLL and divider units to begin outputting the configured
398             frequencies.
399              
400             =cut
401              
402 2         4 async method reset_plls ()
  2         4  
403 2         9 {
404             # This magic value 0xAC doesn't appear in the data sheet itself, but most
405             # of the other drivers use it anyway and it seems to work.
406 2         6 await $self->write_reg( REG_PLLRST, pack "C", 0xAC );
407 2     2 1 4 }
408              
409             =head2 read_multisynth_config
410              
411             $config = await $chip->read_multisynth_config( $idx )
412              
413             Reads and returns the Multisynth frequency divider configuration registers for
414             the given unit (which should be an integer C<0> to C<5>), as a C
415             reference with the following keys:
416              
417             P1 => INT
418             P2 => INT
419             P3 => INT
420             DIVBY4 => BOOL
421             INT => BOOL
422             SRC => "PLLA" | "PLLB"
423             PHOFF => INT
424              
425             Note that this method returns the setting of the appropriate phase-offset
426             register. Even though the datasheet names this as if it were related to the
427             clock output unit, it in fact relates to the Multisynth divider.
428              
429             Additionally, the following extra fields will be inferred from the basic
430             parameters, as a convenience:
431              
432             ratio_a => INT # integral part of ratio
433             ratio_b => INT # numerator of fractional part of ratio
434             ratio_c => INT # denominator of fractional part of ratio
435              
436             ratio => NUM # ratio expressed as a float
437              
438             Note that the integer-only Multisynth units 6 and 7 are not currently
439             supported.
440              
441             =cut
442              
443             bitfield { format => "bytes-LE" }, MS_CLKCTRL =>
444             # REG_CLKnCTRL
445             INT => boolfield( 6 ),
446             SRC => enumfield( 5, qw( PLLA PLLB ) ),
447             ;
448              
449 12         14 async method read_multisynth_config ( $idx )
  12         15  
  12         13  
450 12         25 {
451             # TODO: multisynths 6 and 7 are integer-only and different layout
452              
453 12 50 33     42 $idx >= 0 and $idx <= 5 or croak "Invalid Multisynth choice '$idx'";
454 12         23 my $regbase = REG_MSx_BASE + 8*$idx;
455              
456 12         31 my $parambytes = await $self->cached_read_reg( $regbase, 8 );
457 12         18049 my $clkctrlbyte = await $self->cached_read_reg( REG_CLKCTRL_BASE + $idx, 1 );
458              
459 12         4991 my %config = (
460             unpack_PARAMS( $parambytes ),
461             unpack_MS_CLKCTRL( $clkctrlbyte ),
462             );
463              
464 12         345 my $divby4 = ( ( unpack "x x C", $parambytes ) >> 2 ) & 0x03;
465 12         27 $config{DIVBY4} = !!$divby4;
466              
467 12         26 $config{PHOFF} = unpack "C", await $self->cached_read_reg( REG_CLKx_PHOFF, 1 );
468              
469 12         3728 _infer_ratio \%config;
470              
471 12         50 return \%config;
472 12     12 1 5314 }
473              
474             =head2 change_multisynth_config
475              
476             await $chip->change_multisynth_config( $pll, %changes )
477              
478             Writes changes to the Multisynth frequency divider configuration registers
479             for the given unit. Any fields not specified will retain their current values.
480              
481             As a convenience, the division ratio can be supplied using the three
482             C parameters, rather than the raw C values.
483              
484             To set an integer ratio, this can alternatively be supplied directly by the
485             C parameter. This must be an integer, however. To avoid floating-point
486             inaccuracies in fractional ratios, the three C parameters must be
487             used if the ratio is not a simple integer.
488              
489             =cut
490              
491 4         4 async method change_multisynth_config ( $idx, %changes )
  4         6  
  4         10  
  4         4  
492 4         9 {
493             # TODO: multisynths 6 and 7 are integer-only and different layout
494              
495 4 50 33     19 $idx >= 0 and $idx <= 5 or croak "Invalid Multisynth choice '$idx'";
496 4         9 my $regbase = REG_MSx_BASE + 8*$idx;
497              
498 4         9 my $config = await $self->read_multisynth_config( $idx );
499              
500 4 100       119 if( exists $changes{ratio} ) {
501 1         2 my $ratio = delete $changes{ratio};
502              
503 1 50 33     5 8 <= $ratio and $ratio <= 900 or
504             croak "Cannot set Multisynth divider ratio to $ratio";
505 1 50       2 $ratio == int $ratio or
506             croak "Cannot use 'ratio' to set a non-integer ratio; please supply ratio_a, ratio_b, ratio_c individually";
507              
508 1         2 $changes{ratio_a} = $ratio;
509 1         1 $changes{ratio_b} = 0;
510 1         2 $changes{ratio_c} = 1;
511             }
512              
513 4 50 66     14 if( exists $changes{ratio_a} or exists $changes{ratio_b} or exists $changes{ratio_c} ) {
      33        
514 3         9 _calc_param( \%changes );
515             }
516              
517 4   66     27 exists $changes{$_} and $config->{$_} = $changes{$_} for qw( P1 P2 P3 INT SRC );
518              
519 4         7 my $paramsbytes = pack_PARAMS( %{$config}{qw( P1 P2 P3 )} );
  4         10  
520 4         8 my $clkctrlbyte = pack_MS_CLKCTRL( %{$config}{qw( INT SRC )} );
  4         12  
521 4         195 my $phoff = delete $config->{PHOFF};
522 4 50 33     24 $phoff >= 0 and $phoff < 128 or
523             croak "Invalid PHOFF setting";
524              
525 4         29 await $self->cached_write_reg_masked( $regbase, $paramsbytes, "\xFF\xFF\x03\xFF\xFF\xFF\xFF\xFF" );
526 4         5059 await $self->cached_write_reg_masked( REG_CLKCTRL_BASE + $idx, $clkctrlbyte, "\x60" );
527 4         2377 await $self->cached_write_reg ( REG_CLKx_PHOFF, pack "C", $phoff );
528 4     4 1 10856 }
529              
530             =head2 read_clk_config
531              
532             $config = $chip->read_clk_config( $idx )
533              
534             Reads and returns the clock output pin configuration registers for the given
535             pin index (in the range 0 to 5), as a C reference with the following
536             keys:
537              
538             IDRV => "2mA" | "4mA" | "6mA" | "8mA"
539             SRC => "XTAL" | "CLKIN" | "MS04" | "MSn"
540             INV => BOOL
541             PDN => BOOL
542             DIV => 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128
543             OE => BOOL
544              
545             Note that the C field has positive logic; it is true when the output is
546             enabled (by the C register having a 0 bit in the corresponding
547             position).
548              
549             =cut
550              
551             bitfield { format => "bytes-LE" }, CLKCTRL =>
552             # REG_CLKnCTRL
553             IDRV => enumfield( 0, qw( 2mA 4mA 6mA 8mA ) ),
554             SRC => enumfield( 2, qw( XTAL CLKIN MS04 MSn ) ),
555             INV => boolfield( 4 ),
556             PDN => boolfield( 7 ),
557             # REG_MSnBASE+2
558             DIV => enumfield( 12, qw( 1 2 4 8 16 32 64 128 ) ),
559             ;
560              
561 9         21 async method read_clk_config ( $idx )
  9         12  
  9         11  
562 9         24 {
563 9 50 33     39 $idx >= 0 and $idx <= 7 or croak "Invalid Clk choice '$idx'";
564              
565 9         35 my $bytes = join "",
566             await $self->cached_read_reg( REG_CLKCTRL_BASE + $idx, 1 ),
567             await $self->cached_read_reg( REG_MSx_BASE + 8*$idx + 2, 1 );
568              
569 9         12257 my %config = unpack_CLKCTRL( $bytes );
570              
571 9         503 my $oemask = unpack "C", await $self->cached_read_reg( REG_OEMASK, 1 );
572 9         3716 $config{OE} = !( $oemask & (1<<$idx) );
573              
574 9         41 return \%config;
575 9     9 1 2861 }
576              
577             =head2 change_clk_config
578              
579             await $chip->change_clk_config( $idx, %changes );
580              
581             Writes changes to the clock output pin configuration registers for the given
582             pin index. Any fields not specified will retain their current values.
583              
584             =cut
585              
586 3         4 async method change_clk_config ( $idx, %changes )
  3         6  
  3         7  
  3         5  
587 3         10 {
588 3 50 33     18 $idx >= 0 and $idx <= 7 or croak "Invalid Clk choice '$idx'";
589              
590 3         9 my $config = await $self->read_clk_config( $idx );
591              
592 3         75 $config->{$_} = $changes{$_} for keys %changes;
593              
594             # OE is inverted sense
595 3 50       11 my $oemask = delete $config->{OE} ? "\x00" : "\xFF";
596 3         15 my ( $clkctrl, $ms ) = unpack "(a1)*", pack_CLKCTRL( %$config );
597              
598 3         301 await $self->cached_write_reg_masked( REG_CLKCTRL_BASE + $idx, $clkctrl, "\x9F" );
599 3         2134 await $self->cached_write_reg_masked( REG_MSx_BASE + 8*$idx + 2, $ms, "\xFC" );
600              
601 3         4158 await $self->cached_write_reg_masked( REG_OEMASK, $oemask, pack "C", (1<<$idx) );
602 3     3 1 7816 }
603              
604             =head1 TODO
605              
606             This module is missing support for several chip features, mostly because I
607             only have the MSOP-10 version of the F chip, so I cannot actually
608             test:
609              
610             =over 4
611              
612             =item *
613              
614             Integer-only multisynth units 6 and 7 and their associated clock output pins.
615              
616             =item *
617              
618             The VCXO of F.
619              
620             =item *
621              
622             The CLKIN of F.
623              
624             =back
625              
626             Additionally, lacking a spectrum analyser I cannot confirm operation of:
627              
628             =over 4
629              
630             =item *
631              
632             Spread-spectrum parameters of PLLA.
633              
634             =back
635              
636             =head1 AUTHOR
637              
638             Paul Evans
639              
640             =cut
641              
642             0x55AA;