File Coverage

blib/lib/Device/Chip/Si5351.pm
Criterion Covered Total %
statement 198 204 97.0
branch 23 46 50.0
condition 14 39 35.9
subroutine 25 26 96.1
pod 11 14 78.5
total 271 329 82.3


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