SatCat5
i2c_sc18s602.cc
1 // Copyright 2023-2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 // Device driver for the NXP SC18IS602B I2C-to-SPI bridge
6 //
7 // The SC18IS602B is an SPI master that is controlled through an I2C bus,
8 // allowing indirect control of downstream SPI peripherals. This driver
9 // concerts each SPI transaction into a series of I2C commands.
10 //
11 // Reference: https://www.nxp.com/docs/en/data-sheet/SC18IS602B.pdf
12 //
13 
14 #include <hal_devices/i2c_sc18s602.h>
15 #include <satcat5/log.h>
16 #include <cstring>
17 
18 namespace log = satcat5::log;
24 
25 Sc18is602::Sc18is602(I2cGeneric* i2c, const I2cAddr& devaddr)
26  : m_parent(i2c)
27  , m_devaddr(devaddr)
28  , m_callback(0)
29  , m_busy(0)
30  , m_skip(0)
31 {
32  // Nothing else to initialize.
33 }
34 
35 bool Sc18is602::configure(unsigned spi_mode)
36 {
37  // Sanity check before we start.
38  if (spi_mode > 3) return false;
39 
40  // Function code 0xF0 = Configure SPI interface (Section 7.1.5):
41  // ORDER (bit 5) = 0 (MSB-first)
42  // MODE (bits 3:2) = User-specified 0/1/2/3
43  // RATE (bits 1:0) = 0 (1843 kHz)
44  u8 flags = (u8)(spi_mode << 2);
45  return m_parent->write(m_devaddr, 1, 0xF0, 1, &flags);
46 }
47 
49 {
50  return m_busy > 0;
51 }
52 
54  u8 devidx, const u8* wrdata, u8 rwbytes,
56 {
57  // Sanity check before we start.
58  if (m_busy > 0) return false;
59  if (devidx > 3) return false;
60 
61  // This mode keeps the entire reply.
62  if (execute(devidx, wrdata, rwbytes, 0)) {
63  m_callback = callback;
64  return true;
65  } else {
66  return false;
67  }
68 }
69 
71  u8 devidx, const u8* wrdata, u8 wrbytes, u8 rdbytes,
73 {
74  u8 temp[200];
75 
76  // Sanity check before we start.
77  if (m_busy > 0) return false;
78  if (devidx > 3) return false;
79  if (wrbytes > sizeof(temp)) return false;
80  if (rdbytes > sizeof(temp)) return false;
81  if (wrbytes + rdbytes > sizeof(temp)) return false;
82 
83  // Do we need to zero-pad outgoing data?
84  const u8* rwdata = wrdata; // Use original
85  if (rdbytes > 0) {
86  if (wrbytes > 0) memcpy(temp, wrdata, wrbytes);
87  memset(temp + wrbytes, 0, rdbytes);
88  rwdata = temp; // Use padded copy
89  }
90 
91  // This mode skips first N bytes of reply.
92  if (execute(devidx, rwdata, wrbytes + rdbytes, wrbytes)) {
93  m_callback = callback;
94  return true;
95  } else {
96  return false;
97  }
98 }
99 
100 bool Sc18is602::execute(u8 devidx, const u8* wrdata, u8 rwbytes, u8 skip)
101 {
102  // Callback is set only if we succeed.
103  m_callback = 0;
104  m_skip = skip;
105 
106  // Issue the write command.
107  u32 devmask = (1u << devidx);
108  if (!m_parent->write(m_devaddr, 1, devmask, rwbytes, wrdata, this))
109  return false; // Unable to queue command, abort
110  ++m_busy; // Expect 1st callback event
111 
112  // Can we skip the read command?
113  if (skip >= rwbytes) return true;
114 
115  // Issue the read command.
116  if (!m_parent->read(m_devaddr, 0, 0, rwbytes, this))
117  return false; // Unable to queue command, abort
118  ++m_busy; // Expect 2nd callback event
119 
120  return true; // Success!
121 }
122 
123 void Sc18is602::i2c_done(
124  bool noack, const I2cAddr& devaddr,
125  u32 regaddr, unsigned nread, const u8* rdata)
126 {
127  // Sanity check before proceeding...
128  if (m_busy == 0) {
129  log::Log(log::WARNING, "SC18IS602", "Unexpected callback.");
130  return;
131  } else if (noack) {
132  log::Log(log::WARNING, "SC18IS602", "Missing ACK from I2C address")
133  .write(m_devaddr.m_addr);
134  }
135 
136  // Issue SPI callback on the last event only.
137  if (--m_busy > 0) return;
138  if (m_callback && nread <= m_skip)
139  m_callback->spi_done(0, 0);
140  else if (m_callback)
141  m_callback->spi_done(nread - m_skip, rdata + m_skip);
142 }
Callback interface for cfg::I2cGeneric events.
Definition: cfg_i2c.h:78
Polymorphic API for a generic I2C interface.
Definition: cfg_i2c.h:95
virtual bool read(const satcat5::util::I2cAddr &devaddr, u8 regbytes, u32 regaddr, u8 nread, satcat5::cfg::I2cEventListener *callback=0)=0
Add a read operation to the queue.
virtual bool write(const satcat5::util::I2cAddr &devaddr, u8 regbytes, u32 regaddr, u8 nwrite, const u8 *data, satcat5::cfg::I2cEventListener *callback=0)=0
Add a write operation to the queue.
SPI Event Handler callback for use with SpiGeneric.
Definition: cfg_spi.h:15
Polymorphic API for a generic SPI interface.
Definition: cfg_spi.h:26
Device driver for the NXP SC18IS602B I2C-to-SPI bridge.
Definition: i2c_sc18s602.h:26
bool exchange(u8 devidx, const u8 *wrdata, u8 rwbytes, satcat5::cfg::SpiEventListener *callback=0) override
Queue a read-and-write SPI bus transaction.
Definition: i2c_sc18s602.cc:53
bool configure(unsigned spi_mode)
Configure the SPI mode (0/1/2/3 sets CPOL, CPHA)
Definition: i2c_sc18s602.cc:35
bool query(u8 devidx, const u8 *wrdata, u8 wrbytes, u8 rdbytes, satcat5::cfg::SpiEventListener *callback=0) override
Queue a write-then-read SPI bus transaction.
Definition: i2c_sc18s602.cc:70
bool busy() override
Is the SPI controller currently busy?
Definition: i2c_sc18s602.cc:48
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