File Coverage

blib/lib/Device/Chip/Base/RegisteredI2C.pm
Criterion Covered Total %
statement 126 126 100.0
branch 13 18 72.2
condition 15 18 83.3
subroutine 18 18 100.0
pod 5 6 83.3
total 177 186 95.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, 2015-2023 -- leonerd@leonerd.org.uk
5              
6 1     1   866 use v5.26;
  1         5  
7 1     1   7 use warnings;
  1         2  
  1         70  
8 1     1   6 use Object::Pad 0.800;
  1         9  
  1         47  
9              
10             package Device::Chip::Base::RegisteredI2C 0.26;
11             class Device::Chip::Base::RegisteredI2C :isa(Device::Chip);
12              
13 1     1   511 use utf8;
  1         3  
  1         8  
14              
15 1     1   47 use Future::AsyncAwait 0.38; # async method
  1         17  
  1         5  
16              
17 1     1   60 use Carp;
  1         2  
  1         113  
18              
19 1     1   6 use constant PROTOCOL => "I2C";
  1         37  
  1         105  
20              
21 1     1   7 use constant REG_ADDR_SIZE => 8;
  1         2  
  1         61  
22 1     1   6 use constant REG_DATA_SIZE => 8;
  1         1  
  1         325  
23              
24             =encoding UTF-8
25              
26             =head1 NAME
27              
28             C - base class for drivers of register-oriented I²C chips
29              
30             =head1 DESCRIPTION
31              
32             This subclass of L provides some handy utility methods to
33             implement a chip driver that supports a chip which (largely) operates on the
34             common pattern of registers; that is, that writes to and reads from the chip
35             are performed on numerically-indexed register locations, holding independent
36             values. This is a common pattern that a lot of I²C chips adhere to.
37              
38             =cut
39              
40             =head1 CONSTANTS
41              
42             =cut
43              
44             =head2 REG_DATA_SIZE
45              
46             Gives the number of bits of data each register occupies. Normally this value
47             is 8, but sometimes chips like high-resolution ADCs and DACs might work with a
48             larger size like 16 or 24. This value ought to be a multiple of 8.
49              
50             Overriding this constant to a different value will affect the interpretation
51             of the C<$len> parameter to the register reading and writing methods.
52              
53             =cut
54              
55 2     2 0 4 method REG_DATA_BYTES ()
  2         6  
  2         4  
56             {
57 2         14 my $bytes = int( ( $self->REG_DATA_SIZE + 7 ) / 8 );
58              
59             # cache it for next time
60 2   33     9 my $pkg = ref $self || $self;
61 1     1   9 { no strict 'refs'; *{"${pkg}::REG_DATA_BYTES"} = method () { $bytes }; }
  1     60   2  
  1         3995  
  2         5  
  2         13  
  2         11  
  60         88  
  60         160  
  60         72  
  60         292  
62              
63 2         7 return $bytes;
64             }
65              
66             =head1 METHODS
67              
68             The following methods documented in an C expression return L
69             instances.
70              
71             =cut
72              
73             field @_regcache;
74              
75             =head2 read_reg
76              
77             $val = await $chip->read_reg( $reg, $len );
78              
79             Performs a C I²C transaction, sending the register number as
80             a single byte value, then attempts to read the given number of register slots.
81              
82             =cut
83              
84 8     8 1 370 async method read_reg ( $reg, $len, $__forcecache = 0 )
  8         27  
  8         13  
  8         12  
  8         16  
  8         15  
85 8         15 {
86 8 50       36 $self->REG_ADDR_SIZE == 8 or
87             croak "TODO: Currently unable to cope with REG_ADDR_SIZE != 8";
88              
89 8         44 my $f = $self->protocol->write_then_read( pack( "C", $reg ), $len * $self->REG_DATA_BYTES );
90              
91 8         10983 foreach my $offs ( 0 .. $len-1 ) {
92 8         17 $__forcecache || $_regcache[$reg + $offs] and
93 8     8   1859 $_regcache[$reg + $offs] = $f->then( async sub ( $bytes ) {
  8         16  
  8         11  
94 8         26 return substr $bytes, $offs * $self->REG_DATA_BYTES, $self->REG_DATA_BYTES
95 10 100 100     344 });
96             }
97              
98 8         690 return await $f;
99             }
100              
101             =head2 write_reg
102              
103             await $chip->write_reg( $reg, $val );
104              
105             Performs a C I²C transaction, sending the register number as a single
106             byte value followed by the data to write into it.
107              
108             =cut
109              
110 9     9 1 1489 async method write_reg ( $reg, $val, $__forcecache = 0 )
  9         32  
  9         16  
  9         15  
  9         20  
  9         14  
111 9         19 {
112 9 50       51 $self->REG_ADDR_SIZE == 8 or
113             croak "TODO: Currently unable to cope with REG_ADDR_SIZE != 8";
114              
115 9         33 my $len = length( $val ) / $self->REG_DATA_BYTES;
116              
117 9         31 foreach my $offs ( 0 .. $len-1 ) {
118 12 100 100     118 $__forcecache || defined $_regcache[$reg + $offs] and
119             $_regcache[$reg + $offs] = Future->done(
120             my $bytes = substr $val, $offs * $self->REG_DATA_BYTES, $self->REG_DATA_BYTES
121             );
122             }
123              
124 9         173 await $self->protocol->write( pack( "C", $reg ) . $val );
125             }
126              
127             =head2 cached_read_reg
128              
129             $val = await $chip->cached_read_reg( $reg, $len );
130              
131             Implements a cache around the given register location. Returns the last value
132             known to have been read from or written to the register; or reads it from the
133             actual chip if no interaction has yet been made. Once a cache slot has been
134             created for the register by calling this method, the L and
135             L methods will also keep it updated.
136              
137             This method should be used by chip drivers for interacting with
138             configuration-style registers; that is, registers that the chip itself will
139             treat as simple storage of values. It is not suitable for registers that the
140             chip itself will update.
141              
142             =cut
143              
144 10     10 1 2648 async method cached_read_reg ( $reg, $len )
  10         45  
  10         21  
  10         16  
  10         15  
145 10         19 {
146 10         16 my @f;
147              
148 10         18 my $endreg = $reg + $len;
149              
150 10         47 while( $reg < $endreg ) {
151 19 100       52 if( defined $_regcache[$reg] ) {
152 14         42 push @f, $_regcache[$reg++];
153             }
154             else {
155 5         34 $len = 1;
156 5   100     26 $len++ while $reg + $len < $endreg and
157             !defined $_regcache[ $reg + $len ];
158              
159 5         7 my $thisreg = $reg;
160 5         16 push @f, $self->read_reg( $reg, $len, "forcecache" );
161              
162 5         254 $reg += $len;
163             }
164             }
165              
166 10         60 return join "", await Future->needs_all( @f );
167             }
168              
169             =head2 cached_write_reg
170              
171             await $chip->cached_write_reg( $reg, $val );
172              
173             Optionally writes a new value for the given register location. This method
174             will invoke C except if the register already exists in the cache
175             and already has the given value according to the cache.
176              
177             This method should be used by chip drivers for interacting with
178             configuration-style registers; that is, registers that the chip itself will
179             treat as simple storage of values. It is not suitable for registers that the
180             chip itself will update.
181              
182             =cut
183              
184 8     8 1 1339 async method cached_write_reg ( $reg, $val )
  8         39  
  8         17  
  8         16  
  8         15  
185 8         17 {
186 8         35 my $len = length( $val ) / ( my $datasize = $self->REG_DATA_BYTES );
187              
188             my @current = await Future->needs_all(
189             map {
190 8         43 $_regcache[$reg + $_] // Future->done( "" )
191             } 0 .. $len-1
192             );
193              
194 8         1682 my @want = $val =~ m/(.{$datasize})/sg;
195              
196             # Determine chunks that need rewriting
197 8         21 my @f;
198 8         16 my $offs = 0;
199 8         29 while( $offs < $len ) {
200 15 100       1217 $offs++, next if $current[$offs] eq $want[$offs];
201              
202 6         14 my $startoffs = $offs++;
203 6   100     32 $offs++ while $offs < $len and
204             $current[$offs] ne $want[$offs];
205              
206 6         39 push @f, $self->write_reg( $reg + $startoffs,
207             join( "", @want[$startoffs..$offs-1] ), "forcecache" );
208             }
209              
210 8         7244 await Future->needs_all( @f );
211             }
212              
213             =head2 cached_write_reg_masked
214              
215             await $chip->cached_write_reg_masked( $reg, $val, $mask );
216              
217             Performs a read-modify-write operation to update the given register location.
218             This method will first read the current value of the register for the length
219             of the value and mask. It will then modify this value, taking bits from the
220             value given by I<$val> where the corresponding bit in I<$mask> is set, or
221             leaving them unchanged where the I<$mask> bit is clear. This updated value is
222             then written back.
223              
224             Both the initial read and the subsequent write operation will pass through the
225             cache as for L and L.
226              
227             The length of I<$mask> must equal the length of I<$val>. A mask value with all
228             bits set is equivalent to just calling L. A mask value with
229             all bits clear is equivalent to no update (except that the chip registers may
230             still be read to fill the cache.
231              
232             This method should be used by chip drivers for interacting with
233             configuration-style registers; that is, registers that the chip itself will
234             treat as simple storage of values. It is not suitable for registers that the
235             chip itself will update.
236              
237             =cut
238              
239 1     1 1 91 async method cached_write_reg_masked ( $reg, $val, $mask )
  1         5  
  1         3  
  1         3  
  1         3  
  1         74  
240 1         4 {
241 1 50       7 length $mask == length $val or
242             croak "Require length(mask) == length(val)";
243              
244 1         3 my $readreg = $reg;
245 1         4 my $readlen = ( length $val ) / ( my $datasize = $self->REG_DATA_BYTES );
246 1         3 my $wasval = "";
247              
248 1         5 pos( $mask ) = 0;
249 1   66     43 while( $readlen and $mask =~ m/\G\xFF{$datasize}/g ) {
250 1         4 $readreg++, $readlen--;
251 1         10 $wasval .= "\0" x $datasize;
252             }
253              
254 1 50       32 if( $mask =~ m/(\xFF{$datasize})+$/ ) {
255 1         7 $readlen -= ( $+[0] - $-[0] ) / $datasize;
256             }
257              
258 1 50       8 $wasval .= await $self->cached_read_reg( $readreg, $readlen ) if $readlen > 0;
259              
260 1         452 $val &= $mask;
261 1         5 $val |= ( $wasval & ~$mask );
262              
263 1         5 await $self->cached_write_reg( $reg, $val );
264             }
265              
266             =head1 AUTHOR
267              
268             Paul Evans
269              
270             =cut
271              
272             0x55AA;