SatCat5
cfgbus_i2c.cc
1 // Copyright 2021-2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/cfgbus_i2c.h>
7 #include <satcat5/log.h>
8 #include <satcat5/utils.h>
9 
10 namespace cfg = satcat5::cfg;
11 namespace log = satcat5::log;
12 namespace util = satcat5::util;
13 
14 // Diagnostic options
15 static const unsigned DEBUG_VERBOSE = 0; // Verbosity level (0/1/2)
16 
17 // Define control-register commands:
18 static const u32 CMD_DELAY = 0x0000u;
19 static const u32 CMD_START = 0x0100u;
20 static const u32 CMD_RESTART = 0x0200u;
21 static const u32 CMD_STOP = 0x0300u;
22 static const u32 CMD_TXBYTE = 0x0400u;
23 static const u32 CMD_RXBYTE = 0x0500u;
24 static const u32 CMD_RXFINAL = 0x0600u;
25 static const u32 CFG_NOSTRETCH = (1u << 31);
26 
27 cfg::I2c::I2c(cfg::ConfigBus* cfg, unsigned devaddr)
28  : cfg::MultiSerial(cfg, devaddr, SATCAT5_I2C_MAXCMD,
29  m_txbuff, SATCAT5_I2C_TXBUFF,
30  m_rxbuff, SATCAT5_I2C_RXBUFF)
31 {
32  // No other initialization required.
33 }
34 
36  unsigned refclk_hz, unsigned baud_hz, bool clock_stretch)
37 {
38  if (DEBUG_VERBOSE > 0)
39  log::Log(log::DEBUG, "I2C: Reconfig @ baud").write((u32)baud_hz);
40 
41  u32 div_qtr = util::div_ceil_u32(refclk_hz, 4*baud_hz) - 1;
42  if (clock_stretch)
43  m_ctrl[REGADDR_CFG] = div_qtr;
44  else
45  m_ctrl[REGADDR_CFG] = div_qtr | CFG_NOSTRETCH;
46 }
47 
49 {
50  return !idle();
51 }
52 
54  const util::I2cAddr& devaddr,
55  u8 regbytes, u32 regaddr, u8 nread,
56  cfg::I2cEventListener* callback)
57 {
58  bool ok = enqueue_cmd(devaddr, regbytes, regaddr, 0, 0, nread, callback);
59 
60  if (ok && DEBUG_VERBOSE > 0)
61  log::Log(log::DEBUG, "I2C: Read").write(devaddr.m_addr).write(nread);
62 
63  return ok;
64 }
65 
67  const util::I2cAddr& devaddr,
68  u8 regbytes, u32 regaddr, u8 nwrite, const u8* data,
69  cfg::I2cEventListener* callback)
70 {
71  bool ok = enqueue_cmd(devaddr, regbytes, regaddr, nwrite, data, 0, callback);
72 
73  if (ok && DEBUG_VERBOSE > 0)
74  log::Log(log::DEBUG, "I2C: Write").write(devaddr.m_addr).write(nwrite);
75 
76  return ok;
77 }
78 
79 bool cfg::I2c::enqueue_cmd(
80  const util::I2cAddr& devaddr,
81  u8 regbytes, u32 regaddr,
82  u8 nwrite, const u8* data, u8 nread,
83  cfg::I2cEventListener* callback)
84 {
85  // Sanity check on inputs.
86  if (regbytes > 4) return false;
87 
88  // How many opcodes required for this command?
89  unsigned ndev = 1; // Bytes per device address
90  if (devaddr.is_10b()) ++ndev; // 10-bit address?
91  unsigned ncmd = 2 + ndev; // Start + devaddr + stop
92  ncmd += regbytes + nwrite + nread; // Requested I/O
93  if ((regbytes || nwrite) && nread) // Extra for restart?
94  ncmd += 1 + ndev; // Restart + devaddr
95 
96  // Can we queue this command now?
97  if (!write_check(ncmd, nread)) return false;
98 
99  // Split device address into individual bytes.
100  u16 dev_msb = (devaddr.m_addr >> 8) & 0xFF;
101  u16 dev_lsb = (devaddr.m_addr >> 0) & 0xFF;
102 
103  // Queue up each opcode.
104  m_tx.write_u16(CMD_START);
105  if (regbytes || nwrite) {
106  // Big-endian conversion for regaddr field.
107  u8 regarray[4];
108  util::write_be_u32(regarray, regaddr);
109  const u8* regptr = regarray + 4 - regbytes;
110  // Write command with device register address.
111  if (devaddr.is_10b()) {
112  m_tx.write_u16(CMD_TXBYTE | dev_msb);
113  m_tx.write_u16(CMD_TXBYTE | dev_lsb);
114  } else {
115  m_tx.write_u16(CMD_TXBYTE | dev_lsb);
116  }
117  for (unsigned a = 0 ; a < regbytes ; ++a)
118  m_tx.write_u16(CMD_TXBYTE | regptr[a]);
119  for (unsigned a = 0 ; a < nwrite ; ++a)
120  m_tx.write_u16(CMD_TXBYTE | data[a]);
121  // Follow up with a read transaction?
122  if (nread) m_tx.write_u16(CMD_RESTART);
123  }
124  if (nread) {
125  // Read command with device address.
126  if (devaddr.is_10b()) {
127  m_tx.write_u16(CMD_TXBYTE | dev_msb | 1);
128  m_tx.write_u16(CMD_TXBYTE | dev_lsb);
129  } else {
130  m_tx.write_u16(CMD_TXBYTE | dev_lsb | 1);
131  }
132  for (unsigned a = 1 ; a < nread ; ++a)
133  m_tx.write_u16(CMD_RXBYTE);
134  m_tx.write_u16(CMD_RXFINAL);
135  }
136  m_tx.write_u16(CMD_STOP);
137 
138  // Finalize write and note metadata for later.
139  unsigned idx = write_finish();
140  m_callback[idx] = callback;
141  m_devaddr[idx] = devaddr.m_addr;
142  m_regaddr[idx] = regaddr;
143  return true; // Success!
144 }
145 
146 void cfg::I2c::read_done(unsigned idx)
147 {
148  u8 rxbuff[SATCAT5_I2C_RXBUFF];
149  if (DEBUG_VERBOSE || m_callback[idx]) {
150  // Copy data to working buffer; error flag in the last byte.
151  unsigned nread = m_rx.get_read_ready();
152  m_rx.read_bytes(nread, rxbuff);
153  u8 noack = rxbuff[nread-1];
154  // Optional diagnostic logging.
155  if (DEBUG_VERBOSE) {
156  log::Log msg(log::DEBUG, "I2C: Done");
157  if (noack) msg.write(" (noack)");
158  if (DEBUG_VERBOSE > 1) msg.write(rxbuff, nread-1);
159  }
160  // Notify callback.
161  if (m_callback[idx]) {
162  auto devaddr = util::I2cAddr::native(m_devaddr[idx]);
163  m_callback[idx]->i2c_done(
164  noack, devaddr, m_regaddr[idx], nread-1, rxbuff);
165  }
166  }
167 }
Generic ConfigBus API.
Definition: cfgbus_core.h:124
Callback interface for cfg::I2cGeneric events.
Definition: cfg_i2c.h:78
bool busy() override
Is the I2C controller currently busy?
Definition: cfgbus_i2c.cc:48
void read_done(unsigned idx)
Callback when each transaction is finished.
Definition: cfgbus_i2c.cc:146
I2c(satcat5::cfg::ConfigBus *cfg, unsigned devaddr)
Constructor links to specified ConfigBus address.
Definition: cfgbus_i2c.cc:27
bool read(const satcat5::util::I2cAddr &devaddr, u8 regbytes, u32 regaddr, u8 nread, satcat5::cfg::I2cEventListener *callback=0) override
Add a read operation to the queue.
Definition: cfgbus_i2c.cc:53
bool write(const satcat5::util::I2cAddr &devaddr, u8 regbytes, u32 regaddr, u8 nwrite, const u8 *data, satcat5::cfg::I2cEventListener *callback=0) override
Add a write operation to the queue.
Definition: cfgbus_i2c.cc:66
void configure(unsigned refclk_hz, unsigned baud_hz, bool clock_stretch=true)
Configure the I2C controller.
Definition: cfgbus_i2c.cc:35
Partial driver for the multipurpose serial peripheral.
The Log class creates and formats one log message.
Definition: log.h:195
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
Diagnostic logging to UART and/or Ethernet ports.
Conversion function for I2C device addresses.
Definition: cfg_i2c.h:30
const u16 m_addr
Native internal address representation.
Definition: cfg_i2c.h:67
bool is_10b() const
Is this a 10-bit address?
Definition: cfg_i2c.h:56
static constexpr I2cAddr native(u16 addr)
Create I2C address from its native internal representation.
Definition: cfg_i2c.h:52
Miscellaneous mathematical utility functions.