DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
I2CMasterNode.cpp
Go to the documentation of this file.
1
15
16#include "ers/ers.hpp"
17#include "timing/I2CSlave.hpp"
19#include "timing/toolbox.hpp"
20
21#include <boost/lexical_cast.hpp>
22#include <boost/range/adaptor/map.hpp>
23#include <boost/range/algorithm/copy.hpp>
24
25#include <algorithm>
26#include <string>
27#include <unordered_map>
28#include <utility>
29#include <vector>
30
31namespace dunedaq {
32namespace timing {
33
34UHAL_REGISTER_DERIVED_NODE(I2CMasterNode)
35
36// PRIVATE CONST definitions
37const std::string I2CMasterNode::kPreHiNode = "ps_hi";
38const std::string I2CMasterNode::kPreLoNode = "ps_lo";
39const std::string I2CMasterNode::kCtrlNode = "ctrl";
40const std::string I2CMasterNode::kTxNode = "data";
41const std::string I2CMasterNode::kRxNode = "data";
42const std::string I2CMasterNode::kCmdNode = "cmd_stat";
43const std::string I2CMasterNode::kStatusNode = "cmd_stat";
44
45const uint8_t I2CMasterNode::kStartCmd = 0x80; // 1 << 7 // NOLINT(build/unsigned)
46const uint8_t I2CMasterNode::kStopCmd = 0x40; // 1 << 6 // NOLINT(build/unsigned)
47const uint8_t I2CMasterNode::kReadFromSlaveCmd = 0x20; // 1 << 5 // NOLINT(build/unsigned)
48const uint8_t I2CMasterNode::kWriteToSlaveCmd = 0x10; // 1 << 4 // NOLINT(build/unsigned)
49const uint8_t I2CMasterNode::kAckCmd = 0x08; // 1 << 3 // NOLINT(build/unsigned)
50const uint8_t I2CMasterNode::kInterruptAck = 0x01; // 1 // NOLINT(build/unsigned)
51
52const uint8_t I2CMasterNode::kReceivedAckBit = 0x80; // recvdack = 0x1 << 7 // NOLINT(build/unsigned)
53const uint8_t I2CMasterNode::kBusyBit = 0x40; // busy = 0x1 << 6 // NOLINT(build/unsigned)
54const uint8_t I2CMasterNode::kArbitrationLostBit = 0x20; // arblost = 0x1 << 5 // NOLINT(build/unsigned)
55const uint8_t I2CMasterNode::kInProgressBit = 0x2; // inprogress = 0x1 << 1 // NOLINT(build/unsigned)
56const uint8_t I2CMasterNode::kInterruptBit = 0x1; // interrupt = 0x1 // NOLINT(build/unsigned)
57
58//-----------------------------------------------------------------------------
59I2CMasterNode::I2CMasterNode(const uhal::Node& node)
60 : uhal::Node(node)
61{
63}
64//-----------------------------------------------------------------------------
65
66//-----------------------------------------------------------------------------
68 : uhal::Node(node)
69{
71}
72//-----------------------------------------------------------------------------
73
74//-----------------------------------------------------------------------------
75void
77{
78 // 16 bit clock prescale factor.
79 // formula: m_clockPrescale = (input_frequency / 5 / desired_frequency) -1
80 // for typical IPbus applications: input frequency = IPbus clock = 31.x MHz
81 // target frequency 100 kHz to play it safe (first revision of i2c standard),
82 m_clock_prescale = 0x40;
83 // m_clock_prescale = 0x100;
84
85 // Build the list of slaves
86 // Loop over node parameters. Each parameter becomes a slave node.
87 const std::unordered_map<std::string, std::string>& parameters = this->getParameters();
88 std::unordered_map<std::string, std::string>::const_iterator it;
89 for (it = parameters.begin(); it != parameters.end(); ++it) {
90 uint32_t slave_addr = (boost::lexical_cast<timing::stoul<uint32_t>>(it->second) & 0x7f); // NOLINT(build/unsigned)
91 m_i2c_device_addresses.insert(std::make_pair(it->first, slave_addr));
92 m_i2c_devices.insert(std::make_pair(it->first, new I2CSlave(this, slave_addr)));
93 }
94}
95//-----------------------------------------------------------------------------
96
97//-----------------------------------------------------------------------------
99{
100 std::unordered_map<std::string, I2CSlave*>::iterator it;
101 for (it = m_i2c_devices.begin(); it != m_i2c_devices.end(); ++it) {
102 // Delete slaves
103 delete it->second; // NOLINT
104 }
105}
106//-----------------------------------------------------------------------------
107
108//-----------------------------------------------------------------------------
109std::vector<std::string>
111{
112 std::vector<std::string> slaves;
113
114 boost::copy(m_i2c_device_addresses | boost::adaptors::map_keys, std::back_inserter(slaves));
115 return slaves;
116}
117//-----------------------------------------------------------------------------
118
119//-----------------------------------------------------------------------------
120uint8_t // NOLINT(build/unsigned)
121I2CMasterNode::get_slave_address(const std::string& name) const
122{
123 std::unordered_map<std::string, uint8_t>::const_iterator it = // NOLINT(build/unsigned)
124 m_i2c_device_addresses.find(name);
125 if (it == m_i2c_device_addresses.end()) {
126 throw I2CDeviceNotFound(ERS_HERE, getId(), name);
127 }
128 return it->second;
129}
130//-----------------------------------------------------------------------------
131
132//-----------------------------------------------------------------------------
133const I2CSlave&
134I2CMasterNode::get_slave(const std::string& name) const
135{
136 std::unordered_map<std::string, I2CSlave*>::const_iterator it = m_i2c_devices.find(name);
137 if (it == m_i2c_devices.end()) {
138 throw I2CDeviceNotFound(ERS_HERE, getId(), name);
139 }
140 return *(it->second);
141}
142//-----------------------------------------------------------------------------
143
144//-----------------------------------------------------------------------------
145uint8_t // NOLINT(build/unsigned)
146I2CMasterNode::read_i2c(uint8_t i2c_device_address, uint32_t i2c_reg_address, bool atomic) const // NOLINT(build/unsigned)
147{
148 // // write one word containing the address
149 // std::vector<uint8_t> array(1, i2c_reg_address & 0x7f);
150 // this->write_block_i2c(i2c_device_address, array);
151 // // request the content at the specific address
152 // return this->read_block_i2c(i2c_device_address, 1) [0];
153 return this->read_i2cArray(i2c_device_address, i2c_reg_address, 1, atomic)[0];
154}
155//-----------------------------------------------------------------------------
156
157//-----------------------------------------------------------------------------
158void
159I2CMasterNode::write_i2c(uint8_t i2c_device_address, // NOLINT(build/unsigned)
160 uint32_t i2c_reg_address, // NOLINT(build/unsigned)
161 uint8_t data, // NOLINT(build/unsigned)
162 bool send_stop) const
163{
164 // std::vector<uint8_t> block(2);
165 // block[0] = (i2c_reg_address & 0xff);
166 // block[1] = (data & 0xff);
167 // this->write_block_i2c(i2c_device_address, block);
168
169 this->write_i2cArray(i2c_device_address, i2c_reg_address, { data }, send_stop);
170}
171//-----------------------------------------------------------------------------
172
173//-----------------------------------------------------------------------------
174std::vector<uint8_t> // NOLINT(build/unsigned)
175I2CMasterNode::read_i2cArray(uint8_t i2c_device_address, // NOLINT(build/unsigned)
176 uint32_t i2c_reg_address, // NOLINT(build/unsigned)
177 uint32_t number_of_words, // NOLINT(build/unsigned)
178 bool atomic) const // NOLINT(build/unsigned)
179{
180 // write one word containing the address
181 std::vector<uint8_t> lArray{ (uint8_t)(i2c_reg_address & 0xff) }; // NOLINT(build/unsigned)
182
183 // do not send stop if read transaction is atomic
184 this->write_block_i2c(i2c_device_address, lArray, !atomic);
185
186 // request the content at the specific address,
187 // and do not reset bus at begining of transaction (atomic)
188 return this->read_block_i2c(i2c_device_address, number_of_words, !atomic);
189}
190//-----------------------------------------------------------------------------
191
192//-----------------------------------------------------------------------------
193void
194I2CMasterNode::write_i2cArray(uint8_t i2c_device_address, // NOLINT(build/unsigned)
195 uint32_t i2c_reg_address, // NOLINT(build/unsigned)
196 std::vector<uint8_t> data, // NOLINT(build/unsigned)
197 bool send_stop) const
198{
199 // std::cout << "Writing " << data.size() << " from " << std::showbase << std::hex << i2c_reg_address << " on " <<
200 // (uint32_t)i2c_device_address << std::endl; // HACK
201 std::vector<uint8_t> block(data.size() + 1); // NOLINT(build/unsigned)
202 block[0] = (i2c_reg_address & 0xff);
203
204 for (size_t i(0); i < data.size(); ++i)
205 block[i + 1] = data[i];
206
207 this->write_block_i2c(i2c_device_address, block, send_stop);
208}
209//-----------------------------------------------------------------------------
210
211//-----------------------------------------------------------------------------
212std::vector<uint8_t> // NOLINT(build/unsigned)
213I2CMasterNode::read_i2cPrimitive(uint8_t i2c_device_address, uint32_t number_of_bytes) const // NOLINT(build/unsigned)
214{
215 return this->read_block_i2c(i2c_device_address, number_of_bytes);
216}
217//-----------------------------------------------------------------------------
218
219//-----------------------------------------------------------------------------
220void
221I2CMasterNode::write_i2cPrimitive(uint8_t i2c_device_address, // NOLINT(build/unsigned)
222 const std::vector<uint8_t>& data, // NOLINT(build/unsigned)
223 bool send_stop) const
224{
225 this->write_block_i2c(i2c_device_address, data, send_stop);
226}
227//-----------------------------------------------------------------------------
228
229//-----------------------------------------------------------------------------
230void
231I2CMasterNode::write_block_i2c(uint8_t i2c_device_address, // NOLINT(build/unsigned)
232 const std::vector<uint8_t>& data, // NOLINT(build/unsigned)
233 bool send_stop) const
234{
235 // transmit reg definitions
236 // bits 7-1: 7-bit slave address during address transfer
237 // or first 7 bits of byte during data transfer
238 // bit 0: RW flag during address transfer or LSB during data transfer.
239 // '1' = reading from slave
240 // '0' = writing to slave
241 // command reg definitions
242 // bit 7: Generate start condition
243 // bit 6: Generate stop condition
244 // bit 5: Read from slave
245 // bit 4: Write to slave
246 // bit 3: 0 when acknowledgement is received
247 // bit 2:1: Reserved
248 // bit 0: Interrupt acknowledge. When set, clears a pending interrupt
249
250 // Reset bus before beginning
251 reset();
252
253 // Open the connection and send the slave address, bit 0 set to zero
254 send_i2c_command_and_write_data(kStartCmd, (i2c_device_address << 1) & 0xfe);
255
256 for (unsigned ibyte = 0; ibyte < data.size(); ibyte++) {
257
258 // Send stop if last element of the array (and not vetoed)
259 uint8_t cmd = (((ibyte == data.size() - 1) && send_stop) ? kStopCmd : 0x0); // NOLINT(build/unsigned)
260
261 // Push the byte on the bus
262 send_i2c_command_and_write_data(cmd, data[ibyte]);
263 }
264}
265//-----------------------------------------------------------------------------
266
267//-----------------------------------------------------------------------------
268std::vector<uint8_t> // NOLINT(build/unsigned)
269I2CMasterNode::read_block_i2c(uint8_t i2c_device_address, uint32_t number_of_bytes, bool send_reset) const // NOLINT(build/unsigned)
270{
271 // transmit reg definitions
272 // bits 7-1: 7-bit slave address during address transfer
273 // or first 7 bits of byte during data transfer
274 // bit 0: RW flag during address transfer or LSB during data transfer.
275 // '1' = reading from slave
276 // '0' = writing to slave
277 // command reg definitions
278 // bit 7: Generate start condition
279 // bit 6: Generate stop condition
280 // bit 5: Read from slave
281 // bit 4: Write to slave
282 // bit 3: 0 when acknowledgement is received
283 // bit 2:1: Reserved
284 // bit 0: Interrupt acknowledge. When set, clears a pending interrupt
285
286 // Reset bus before beginning
287 if (send_reset)
288 {
289 reset();
290 }
291
292 // Open the connection & send the target i2c address. Bit 0 set to 1 (read)
293 send_i2c_command_and_write_data(kStartCmd, (i2c_device_address << 1) | 0x01);
294
295 std::vector<uint8_t> lArray; // NOLINT(build/unsigned)
296 for (unsigned ibyte = 0; ibyte < number_of_bytes; ibyte++) {
297
298 uint8_t cmd = ((ibyte == number_of_bytes - 1) ? (kStopCmd | kAckCmd) : 0x0); // NOLINT(build/unsigned)
299
300 // Push the cmd on the bus, retrieve the result and put it in the arrary
301 lArray.push_back(send_i2c_command_and_read_data(cmd));
302 }
303 return lArray;
304}
305//-----------------------------------------------------------------------------
306
307//-----------------------------------------------------------------------------
308bool
309I2CMasterNode::ping(uint8_t i2c_device_address, bool throw_excp) const // NOLINT(build/unsigned)
310{
311 // Reset bus before beginning
312 reset();
313
314 try {
315 send_i2c_command_and_write_data(kStartCmd, (i2c_device_address << 1) | 0x01);
316 send_i2c_command_and_read_data(kStopCmd | kAckCmd);
317 return true;
318 } catch (const timing::I2CException& excp) {
319 if (throw_excp) {
320 throw excp;
321 }
322 return false;
323 }
324}
325//-----------------------------------------------------------------------------
326
327//-----------------------------------------------------------------------------
328std::vector<uint8_t> // NOLINT(build/unsigned)
330{
331
332 std::vector<uint8_t> address_vector; // NOLINT(build/unsigned)
333
334 // Reset bus before beginning
335 reset();
336
337 for (uint8_t iaddr(0); iaddr < 0x7f; ++iaddr) { // NOLINT(build/unsigned)
338 // Open the connection & send the target i2c address. Bit 0 set to 1 (read)
339 try {
340 send_i2c_command_and_write_data(kStartCmd, (iaddr << 1) | 0x01);
342 } catch (const timing::I2CException& excp) {
343 // TIMING_LOG(kError) << std::showbase << std::hex << (uint32_t)iaddr << " " << excp.what();
344 continue;
345 }
346 address_vector.push_back(iaddr);
347 // TIMING_LOG(kNotice) << std::showbase << std::hex << (uint32_t)iaddr << " found";
348 }
349
350 return address_vector;
351}
352//-----------------------------------------------------------------------------
353
354//-----------------------------------------------------------------------------
355void
357{
358 // Resets the I2C bus
359 //
360 // This function does the following:
361 // 1) Disables the I2C core
362 // 2) Sets the clock prescale registers
363 // 3) Enables the I2C core
364 // 4) Sets all writable bus-master registers to default values
365
366 auto ctrl = getNode(kCtrlNode).read();
367 auto pre_hi = getNode(kPreHiNode).read();
368 auto pre_lo = getNode(kPreLoNode).read();
369 getClient().dispatch();
370
371 bool full_reset(false);
372
373 full_reset = (m_clock_prescale != (pre_hi << 8) + pre_lo);
374
375 if (full_reset) {
376 // disable the I2C core
377 getNode(kCtrlNode).write(0x00);
378 getClient().dispatch();
379 // set the clock prescale
380 getNode(kPreHiNode).write((m_clock_prescale & 0xff00) >> 8);
381 // getClient().dispatch();
382 getNode(kPreLoNode).write(m_clock_prescale & 0xff);
383 // getClient().dispatch();
384 // set all writable bus-master registers to default values
385 getNode(kTxNode).write(0x00);
386 getNode(kCmdNode).write(0x00);
387 getClient().dispatch();
388
389 // enable the I2C core
390 getNode(kCtrlNode).write(0x80);
391 getClient().dispatch();
392 } else {
393 // set all writable bus-master registers to default values
394 getNode(kTxNode).write(0x00);
395 getNode(kCmdNode).write(0x00);
396 getClient().dispatch();
397 }
398}
399//-----------------------------------------------------------------------------
400
401//-----------------------------------------------------------------------------
402uint8_t // NOLINT(build/unsigned)
403I2CMasterNode::send_i2c_command_and_read_data(uint8_t command) const // NOLINT(build/unsigned)
404{
405
406 assert(!(command & kWriteToSlaveCmd));
407
408 uint8_t full_cmd = command | kReadFromSlaveCmd; // NOLINT(build/unsigned)
409 TLOG_DEBUG(10) << ">> sending read cmd = " << format_reg_value((uint32_t)full_cmd); // NOLINT(build/unsigned)
410
411 // Force the read bit high and set them cmd bits
412 getNode(kCmdNode).write(full_cmd);
413 getClient().dispatch();
414
415 // Wait for transaction to finish. Require idle bus at the end if stop bit is high)
416 wait_until_finished(/*req ack*/ false, command & kStopCmd);
417
418 // Pull the data out of the rx register.
419 uhal::ValWord<uint32_t> result = getNode(kRxNode).read(); // NOLINT(build/unsigned)
420 getClient().dispatch();
421
422 TLOG_DEBUG(10) << "<< receive data = " << format_reg_value((uint32_t)result); // NOLINT(build/unsigned)v
423
424 return (result & 0xff);
425}
426//-----------------------------------------------------------------------------
427
428//-----------------------------------------------------------------------------
429void
430I2CMasterNode::send_i2c_command_and_write_data(uint8_t command, uint8_t data) const // NOLINT(build/unsigned)
431{
432
433 //
434 assert(!(command & kReadFromSlaveCmd));
435
436 uint8_t full_cmd = command | kWriteToSlaveCmd; // NOLINT(build/unsigned)
437 std::stringstream debug_stream;
438 debug_stream << ">> sending write cmd = " << std::showbase << std::hex << (uint32_t)full_cmd // NOLINT(build/unsigned)
439 << " data = " << std::showbase << std::hex << (uint32_t)data; // NOLINT(build/unsigned)
440 TLOG_DEBUG(10) << debug_stream.str();
441
442 // write the payload
443 getNode(kTxNode).write(data);
444 getClient().dispatch();
445
446 // Force the write bit high and set them cmd bits
447 getNode(kCmdNode).write(full_cmd);
448
449 // Run the commands and wait for transaction to finish
450 getClient().dispatch();
451
452 // Wait for transaction to finish. Require idle bus at the end if stop bit is high
453 // wait_until_finished(req_hack, requ_idle)
454 wait_until_finished( true, command & kStopCmd); // NOLINT
455}
456//-----------------------------------------------------------------------------
457
458//-----------------------------------------------------------------------------
459void
460I2CMasterNode::wait_until_finished(bool require_acknowledgement, bool require_bus_idle_at_end) const
461{
462 // Ensures the current bus transaction has finished successfully
463 // before allowing further I2C bus transactions
464 // This method monitors the status register
465 // and will not allow execution to continue until the
466 // I2C bus has completed properly. It will throw an exception
467 // if it picks up bus problems or a bus timeout occurs.
468 const unsigned max_retry = 20;
469 unsigned attempt = 1;
470 bool received_acknowledge, busy;
471
472 const uhal::Node& status_node = getNode(kStatusNode);
473
474 while (attempt <= max_retry) {
475 usleep(10);
476 // Get the status
477 uhal::ValWord<uint32_t> i2c_status = status_node.read(); // NOLINT(build/unsigned)
478 getClient().dispatch();
479
480 received_acknowledge = !(i2c_status & kReceivedAckBit);
481 busy = (i2c_status & kBusyBit);
482 bool arbitration_lost = (i2c_status & kArbitrationLostBit);
483 bool transfer_in_progress = (i2c_status & kInProgressBit);
484
485 if (arbitration_lost) {
486 // This is an instant error at any time
487 throw I2CBusArbitrationLost(ERS_HERE, getId());
488 }
489
490 if (!transfer_in_progress) {
491 // The transfer looks to have completed successfully,
492 // pending further checks
493 break;
494 }
495
496 attempt += 1;
497 }
498
499 // At this point, we've either had too many retries, or the
500 // Transfer in Progress (TIP) bit went low. If the TIP bit
501 // did go low, then we do a couple of other checks to see if
502 // the bus operated as expected:
503
504 if (attempt > max_retry) {
505 throw I2CTransactionTimeout(ERS_HERE, getId());
506 }
507
508 if (require_acknowledgement && !received_acknowledge) {
509 throw I2CNoAcknowledgeReceived(ERS_HERE, getId());
510 }
511
512 if (require_bus_idle_at_end && busy) {
513 throw I2CTransferFinishedBusStillBusy(ERS_HERE, getId());
514 }
515}
516
517} // namespace timing
518} // namespace dunedaq
#define ERS_HERE
OpenCode I2C interface to a ipbus node.
std::unordered_map< std::string, uint8_t > m_i2c_device_addresses
Slaves.
static const std::string kCtrlNode
static const std::string kStatusNode
void send_i2c_command_and_write_data(uint8_t command, uint8_t data) const
virtual void write_i2c(uint8_t i2c_device_address, uint32_t i2c_reg_address, uint8_t data, bool send_stop=true) const
std::vector< uint8_t > scan() const
I2CMasterNode(const uhal::Node &node)
virtual void write_i2cPrimitive(uint8_t i2c_device_address, const std::vector< uint8_t > &data, bool send_stop=true) const
static const std::string kCmdNode
virtual uint8_t read_i2c(uint8_t i2c_device_address, uint32_t i2c_reg_address, bool atomic=false) const
commodity functions
static const std::string kPreHiNode
IPBus register names for i2c bus.
uint16_t m_clock_prescale
clock prescale factor
bool ping(uint8_t i2c_device_address, bool throw_excp=false) const
static const uint8_t kInProgressBit
static const std::string kPreLoNode
static const uint8_t kArbitrationLostBit
virtual uint8_t get_slave_address(const std::string &name) const
uint8_t send_i2c_command_and_read_data(uint8_t command) const
std::unordered_map< std::string, I2CSlave * > m_i2c_devices
I2C slaves attached to this node.
virtual std::vector< uint8_t > read_i2cPrimitive(uint8_t i2c_device_address, uint32_t number_of_bytes) const
static const uint8_t kWriteToSlaveCmd
virtual std::vector< std::string > get_slaves() const
virtual std::vector< uint8_t > read_block_i2c(uint8_t i2c_device_address, uint32_t number_of_bytes, bool send_reset=true) const
static const std::string kTxNode
virtual void write_i2cArray(uint8_t i2c_device_address, uint32_t i2c_reg_address, std::vector< uint8_t > data, bool send_stop=true) const
static const uint8_t kInterruptBit
static const uint8_t kReceivedAckBit
virtual const I2CSlave & get_slave(const std::string &name) const
virtual void write_block_i2c(uint8_t i2c_device_address, const std::vector< uint8_t > &data, bool send_stop=true) const
void wait_until_finished(bool require_acknowledgement=true, bool require_bus_idle_at_end=false) const
static const uint8_t kReadFromSlaveCmd
virtual std::vector< uint8_t > read_i2cArray(uint8_t i2c_device_address, uint32_t i2c_reg_address, uint32_t number_of_words, bool atomic=false) const
static const std::string kRxNode
static const uint8_t kInterruptAck
#define TLOG_DEBUG(lvl,...)
Definition Logging.hpp:112
std::string format_reg_value(T reg_value, uint32_t base)
Definition toolbox.hxx:117
The DUNE-DAQ namespace.
Cannot create std::string fatal Incorrect parameters