tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/as560x/i2c_register.go (about)

     1  package as560x // import tinygo.org/x/drivers/ams560x
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  
     7  	"tinygo.org/x/drivers"
     8  	"tinygo.org/x/drivers/internal/legacy"
     9  )
    10  
    11  // registerAttributes is a bitfield of attributes for a register
    12  type registerAttributes uint8
    13  
    14  const (
    15  	// reg_read indicates that the register is readable
    16  	reg_read registerAttributes = 1 << iota
    17  	// reg_write indicates that the register is writeable
    18  	reg_write
    19  	// reg_program indicates that the register can be permanently programmed ('BURNed')
    20  	reg_program
    21  )
    22  
    23  var (
    24  	errRegisterNotReadable  = errors.New("Register is not readable")
    25  	errRegisterNotWriteable = errors.New("Register is not writeable")
    26  )
    27  
    28  // i2cRegister encapsulates the address, structure and read/write logic for a register on a AS560x device
    29  type i2cRegister struct {
    30  	// host is the 'host register' for virtual registers. Physical/root registers have this set to self
    31  	host *i2cRegister
    32  	// address is the i2c address of the register. For 2-byte (word) addresses it's the low byte which holds the MSBs
    33  	address uint8
    34  	// shift is the number of bits the value is 'left shifted' into the register byte/word (0-15)
    35  	shift uint16
    36  	// mask is a bitwise mask applied to the register AFTER 'right shifting' to mask the register value
    37  	mask uint16
    38  	// num_bytes is the width of the register in bytes, 1 or 2.
    39  	num_bytes uint8
    40  	// attributes holds the register attributes. A bitfield of REG_xyz constants
    41  	attributes registerAttributes
    42  	// cached indicates whether we are holding a cached value of the register in value
    43  	cached bool
    44  	// value can be used as a 'cache' of the register's value for writeable registers.
    45  	value uint16
    46  }
    47  
    48  // newI2CRegister returns a pointer to a new i2cRegister with no cached value
    49  func newI2CRegister(address uint8, shift uint16, mask uint16, num_bytes uint8, attributes registerAttributes) *i2cRegister {
    50  	reg := &i2cRegister{
    51  		address:    address,
    52  		shift:      shift,
    53  		mask:       mask,
    54  		num_bytes:  num_bytes,
    55  		attributes: attributes,
    56  	}
    57  	// root registers host themselves
    58  	reg.host = reg
    59  	return reg
    60  }
    61  
    62  // newVirtualRegister returns a pointer to a new i2cRegister with the given host register and shift/mask.
    63  func newVirtualRegister(host *i2cRegister, shift uint16, mask uint16) *i2cRegister {
    64  	return &i2cRegister{
    65  		host:       host,
    66  		address:    host.address,
    67  		shift:      shift,
    68  		mask:       mask,
    69  		num_bytes:  host.num_bytes,
    70  		attributes: host.attributes,
    71  	}
    72  }
    73  
    74  // invalidate invalidates any cached value for the register and forces an I2C read on the next read()
    75  func (r *i2cRegister) invalidate() {
    76  	r.host.cached = false
    77  	r.host.value = 0
    78  }
    79  
    80  // readShiftAndMask is an internal method to read a value for the register over the given I2C bus from the device with the given address applying the given shift and mask
    81  func (r *i2cRegister) readShiftAndMask(bus drivers.I2C, deviceAddress uint8, shift uint16, mask uint16) (uint16, error) {
    82  	if r.host.attributes&reg_read == 0 {
    83  		return 0, errRegisterNotReadable
    84  	}
    85  
    86  	// Only read over I2C if we don't have the host register value cached
    87  	var val uint16 = r.host.value
    88  	if !r.host.cached {
    89  		// To avoid an alloc we always use an array of 2 bytes
    90  		var buffer [2]byte
    91  		var buf []byte
    92  		if r.host.num_bytes < 2 {
    93  			buf = buffer[:1]
    94  		} else {
    95  			buf = buffer[:]
    96  		}
    97  		// Read the host register over I2C
    98  		err := legacy.ReadRegister(bus, deviceAddress, r.host.address, buf)
    99  		if nil != err {
   100  			return 0, err
   101  		}
   102  		// Unpack data from I2C
   103  		if r.host.num_bytes > 1 {
   104  			val = binary.BigEndian.Uint16(buf)
   105  		} else {
   106  			val = uint16(buf[0])
   107  		}
   108  		// cache this value if the host register is writeable. Note we cache the entire buffer without applying shift/mask
   109  		if r.host.attributes&reg_write != 0 {
   110  			r.host.value = val
   111  			r.host.cached = true
   112  		}
   113  	}
   114  	// Shift and mask the value before returning
   115  	val >>= shift
   116  	val &= mask
   117  	return val, nil
   118  }
   119  
   120  // read reads a value for the register over the given I2C bus from the device with the given address.
   121  func (r *i2cRegister) read(bus drivers.I2C, deviceAddress uint8) (uint16, error) {
   122  	return r.readShiftAndMask(bus, deviceAddress, r.shift, r.mask)
   123  }
   124  
   125  // write writes a value for the register over the given I2C bus to the device with the given address.
   126  func (r *i2cRegister) write(bus drivers.I2C, deviceAddress uint8, value uint16) error {
   127  	if r.host.attributes&reg_write == 0 {
   128  		return errRegisterNotWriteable
   129  	}
   130  	var newValue uint16 = 0
   131  	// Data sheet tells us to do a read first, modify only the desired bits and then write back
   132  	// since (quote:) 'Blank fields may contain factory settings'
   133  	// We will also need to do this anyway to support virtualRegister mappings on some registers
   134  	// (e.g. CONF/STATUS)
   135  	if (r.host.attributes & reg_read) > 0 { // not all registers are readable, e.g. BURN
   136  		// read the host register's entire host byte/word, regardless of shift & mask
   137  		readValue, error := r.readShiftAndMask(bus, deviceAddress, 0, 0xffff)
   138  		if error != nil {
   139  			return error
   140  		}
   141  		// Zero-out ONLY the relevant bits in newValue we just read
   142  		readValue &= (0xffff ^ (r.mask << r.shift))
   143  		newValue = readValue
   144  	}
   145  	// Mask the new value and shift it into place
   146  	value &= r.mask
   147  	value <<= r.shift
   148  	// OR the masked & shifted value back into newValue to be written
   149  	newValue |= value
   150  	// Pack newValue into a byte buffer to write. To avoid an alloc we always use an array of 2 bytes
   151  	var buffer [2]byte
   152  	var buf []byte
   153  	if r.host.num_bytes < 2 {
   154  		buf = buffer[:1]
   155  		buf[0] = uint8(newValue & 0xff)
   156  	} else {
   157  		buf = buffer[:]
   158  		binary.BigEndian.PutUint16(buf, newValue)
   159  	}
   160  
   161  	// Write the register from the buffer over I2C
   162  	err := legacy.WriteRegister(bus, deviceAddress, r.host.address, buf)
   163  	// after successful I2C write, cache this value if the host register (if also readable)
   164  	// Note we cache the entire buffer without applying shift/mask
   165  	if nil == err && r.host.attributes&reg_read != 0 {
   166  		r.host.value = newValue
   167  		r.host.cached = true
   168  	}
   169  	return err
   170  }