This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM335x I2C Slave driver for Qnx

hi,

I am developing an interrupt based I2C multimaster driver on AM335x under QNX6.6. This is intended as an interrupt based alternative to the polling driver as in AM335x I2C multi-master/slave mode  . I am in the same team those guys, and we agreed I would post separately so as not to muddy the other thread which at present only concerns a polling-based driver. I am familiar with Steve’s polling driver and some of the issues being following up with TI there.

The first stage was to develop an interrupt based driver for the 3 basic modes we require, standalone:-

- Master TX : basically working. Here, I am using the same approach as in the AM335x QNX6.6 BSP I2C driver.

- Slave RX: basically working. I am using RXTRSH=0 hence reading 1 byte at a time, looking for RRDY interrupt.

- Slave TX: not working. As is reqd for slave TX, I am using TXTRSH=0, so sending 1 byte at a time. I continue sending data, and expect the Master to send a NACK. However,   I can get at most 1 data byte, then the bus is held low. After the 1st byte is sent (signalled by XRDY), I write another byte to the FIFO, and I clear XRDY. Unfortunately, XRDY bit is not asserted again. I am of course checking that AERR,NACK etc are not being set.

 I am focussing on debugging slave TX in isolation now, as the other modes are more or less ok. For this purpose I have disabled the master side of the code.

My state machine handles transition into Slave TX or RX based on the interrupt flags.

 In QNX I am using InterruptAttachEvent() rather than InterruptAttach() so there is no ISR as such. Handling is done in user space so can be debugged more easily. For the slave thread, I am waiting for a message on the channel attached to the interrupt, upon which I read the IRQSTATUS_RAW register immediately once to get the status and perform handling for master or slave – based on AAS. Clearing is done by writing to IRQSTATUS based on the flags and the states entered, and then the driver loops back to wait for another interrupt.

 Pseudocode for slave (master not shown) is as below.

 I attach a screenshot showing the timings for the failed Slave TX and a log.

 FYI I have also tried variations on this such as:-

-       not clearing XRDY

- using XUDF as indication of ongoing transmit (in the absence of XRDY.)

 Assumptions:

-I am not going to clear AAS until ARDY appears.

-I am not clearing XUDF, since writing to the FIFO should do that.

 

Please can you give any advice as to why XRDY may stop appearing.

 

 

Init:

dev->iid = InterruptAttachEvent(dev->intr,…)

create/call SlaveIsThread()

reset i2c

Slave Thread

while(1)

{

If timed out: reset I2C;

If (slave state==idle)

{

do once per entry into slave state:

{

set own address;

out16( pdev->regbase + AM335x_I2C_IE_SET,    (  AM335x_I2C_XUDF | AM335x_I2C_AAS |

                AM335x_I2C_AERR | AM335x_I2C_XRDY|

                AM335x_I2C_RRDY | AM335x_I2C_ARDY | AM335x_I2C_NACK ) )

set RX/TXTHRES to 1 byte;  reset the RX/TX FIFOs;

}

wait until bus not busy: IRQSTATUS_RAW & BB, if timed out, try again

}

enable I2C in I2C_CON slave mode;

InterruptUnmask(pdev->intr,pdev->iid);

WAIT FOR INTERRUPT()

If (SlaveRxComplete) – copy data to host buffer etc;

If (SlaveTxComplete) – just start again;

}

Wait for interrupt

if(MsgReceivePulse(dev->chid, &pulse,….)

if timeout, start waiting again;

else:

InterruptStatusReg = in16(dev->regbase + AM335x_I2C_STAT_RAW);

if(InterruptStatusReg & AAS) : handleSlave(InterruptStatusReg );

else if(InterruptStatusReg & (BB | XRDY | XDR) ): handleMaster(InterruptStatusReg ); // NB! Master RX not reqd..

handleSlave

 if((InterruptStatusReg & (XRDY | XUDF))==0) // slave RX

{

if(InterruptStatusReg & RRDY): read byte, clear RRDY

if(InterruptStatusReg & AM335x_I2C_ARDY) : SlaveRxComplete = 1;

}

else if(InterruptStatusReg & (AM335x_I2C_XRDY))   // Slave Transmit

{

if we are within our slave buffer bounds: write next byte 1 byte to FIFO; clear XRDY;

else write 0;

}

else

{

if(InterruptStatusReg & ARDY): clearValue |= (ARDY | AAS | BF)

}

Screenshot:

rmi2c:[1642] Reg_GetBusActive mode=SLAVE
rmi2c:[194]:i2c_wait_bus_not_busy: entry (CON 0x8000 STATRAW 0x0)
rmi2c:[227] i2c_wait_bus_not_busy: (CTRREG 0x8000 STSREG 0x0)
rmi2c:[250] i2c_wait_bus_not_busy: return 0 (CTRREG 0x8000 STSREG 0x0)
rmi2c:Reg_GetBusActive: <SLAVE>  bus is free
rmi2c:[926]  SlaveIsThread: Bus is free, enabling i2c module. own addr=0x45
rmi2c:[596] omap_wait_status: mode=<SLAVE> entry: CON=0x8000 IE set= 0x29e STAT_RAW= 0x0
rmi2c:[632]  omap_wait_status: <SLAVE> AM335x_I2C_AAS slave state=10 STAT=0x1600 RAW=0x1600
rmi2c:[180] handleSlave:entry: STATRAW=0x1600
rmi2c:[307] handleSlave: state =  I2C_SLAVE_IDLE: TX started (BB|XUDF) but XRDY not on yet. setting mode I2C_SLAVE_TX
rmi2c:[1626] Reg_InterruptsClear. AM335x_I2C_IRQSTAT_RAW= 0x1610
rmi2c:[1627] Reg_InterruptsClear. Writing to AM335x_I2C_STAT 0x0
rmi2c:[638]  omap_wait_status: <SLAVE> done handleSlave
rmi2c:[671]  omap_wait_status: <SLAVE> done
rmi2c:  [958] SlaveIsThread: gotIntr[I2C_INDEX_SLAVE]==1 - released from omap_wait_status()
rmi2c:[963] I2C Slave MaxIntr:1
rmi2c:  [1002] SlaveIsThread: end while: Slave TX STAT=0x1610 RAW=0x1610
rmi2c:[596] omap_wait_status: mode=<SLAVE> entry: CON=0x8000 IE set= 0x29e STAT_RAW= 0x1610
rmi2c:[632]  omap_wait_status: <SLAVE> AM335x_I2C_AAS slave state=12 STAT=0x1610 RAW=0x1610
rmi2c:[180] handleSlave:entry: STATRAW=0x1610
rmi2c:[388] handleSlave: I2C_SLAVE_TX
rmi2c:[410] handleSlave: I2C_SLAVE_TX slave TX : XRDY/XUDF
rmi2c:[433] handleSlave: state = I2C_SLAVE_TX: SlaveTxSentBytes=1 data=0x36(6)  <<<<<<<<<<<<<<<<<<<<<<<<< {PIL} 1st byte ok
rmi2c:[1626] Reg_InterruptsClear. AM335x_I2C_IRQSTAT_RAW= 0x1610
rmi2c:[1627] Reg_InterruptsClear. Writing to AM335x_I2C_STAT 0x10   <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< clear XRDY
rmi2c:[638]  omap_wait_status: <SLAVE> done handleSlave
rmi2c:[671]  omap_wait_status: <SLAVE> done
rmi2c:  [958] SlaveIsThread: gotIntr[I2C_INDEX_SLAVE]==1 - released from omap_wait_status()
rmi2c:[963] I2C Slave MaxIntr:1
rmi2c:  [1002] SlaveIsThread: end while: Slave TX STAT=0x1600 RAW=0x1600
rmi2c:[596] omap_wait_status: mode=<SLAVE> entry: CON=0x8000 IE set= 0x29e STAT_RAW= 0x1600
rmi2c:[632]  omap_wait_status: <SLAVE> AM335x_I2C_AAS slave state=12 STAT=0x1600 RAW=0x1600
rmi2c:[180] handleSlave:entry: STATRAW=0x1600
rmi2c:[388] handleSlave: I2C_SLAVE_TX
rmi2c:[410] handleSlave: I2C_SLAVE_TX slave TX : XRDY/XUDF
rmi2c:[433] handleSlave: state = I2C_SLAVE_TX: SlaveTxSentBytes=2 data=0x37(7) <<<<<<<<<<<<<<<<<<<<<<<<<<<<< {PIL} 2nd byte ok
rmi2c:[1626] Reg_InterruptsClear. AM335x_I2C_IRQSTAT_RAW= 0x1200
rmi2c:[1627] Reg_InterruptsClear. Writing to AM335x_I2C_STAT 0x0   <<<<<<<<<<< not clearing anything
rmi2c:[638]  omap_wait_status: <SLAVE> done handleSlave
rmi2c:[671]  omap_wait_status: <SLAVE> done
rmi2c:  [958] SlaveIsThread: gotIntr[I2C_INDEX_SLAVE]==1 - released from omap_wait_status()
rmi2c:  [1002] SlaveIsThread: end while: Slave TX STAT=0x1200 RAW=0x1200

rmi2c:[596] omap_wait_status: mode=<SLAVE> entry: CON=0x8000 IE set= 0x29e STAT_RAW= 0x1200   <<<<<<<<<<<<< but now, only BB and AAS set
rmi2c:[632]  omap_wait_status: <SLAVE> AM335x_I2C_AAS slave state=12 STAT=0x1200 RAW=0x1200
rmi2c:[180] handleSlave:entry: STATRAW=0x1200
rmi2c:[388] handleSlave: I2C_SLAVE_TX
rmi2c:[447] handleSlave: SLAVE TX ??? que? statreg=0x1200          <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< nothing to handle since XDRY / XUDF both 0
rmi2c:[1626] Reg_InterruptsClear. AM335x_I2C_IRQSTAT_RAW= 0x1200
rmi2c:[1627] Reg_InterruptsClear. Writing to AM335x_I2C_STAT 0x0
rmi2c:[638]  omap_wait_status: <SLAVE> done handleSlave
rmi2c:[671]  omap_wait_status: <SLAVE> done
rmi2c:  [958] SlaveIsThread: gotIntr[I2C_INDEX_SLAVE]==1 - released from omap_wait_status()
rmi2c:  [1002] SlaveIsThread: end while: Slave TX STAT=0x1200 RAW=0x1200

  • Hi,

    I will ask the I2C experts to comment on this.
  • Paul L said:
    - Slave TX: not working. As is reqd for slave TX, I am using TXTRSH=0, so sending 1 byte at a time. I continue sending data, and expect the Master to send a NACK. However,   I can get at most 1 data byte, then the bus is held low. After the 1st byte is sent (signalled by XRDY), I write another byte to the FIFO, and I clear XRDY. Unfortunately, XRDY bit is not asserted again.

    Let's look at this a moment from the other end of the bus, i.e. the master receiver.  The normal/proper operation for a master receiver reading N bytes is to ACK the first N-1 bytes and then NACK the Nth byte so that the slave transmitter knows not to send anything further.

    So given that context, does that help us to narrow down this issue further?  In other words, how many bytes is the master receiver attempting to read?  If for example your case right now is a single byte read (where the master is expected to NACK), then perhaps that's indicative of an issue with the LAST word of a N byte read.  It would be useful to see how it behaves in the context of a N byte read.  Specifically, does the issue relate to the first byte or final byte?

  • Hi Brad, thanks, as I just mentioned to the other guys on the confcall we just had - I have had SlaveTX working also now, finishing with a NACK as you say.
    i.e. all 3 modes have been somewhat working. with these caveats:

    i) I've not tested any multimaster operation as yet, and I've not managed to establish a session with the gateway due to at least the MTX issue below.
    ii) If my state machine is handling a SLV read, but if the I2C peripheral indicates that a SLV write is ongoing (via AAS|XRDY), then I've been giving priority to the bus state- ie. I kill off the slave RD in the driver and move to SLTX mode. Doing this does mean that some SLV rds fail, and need to be re-issued, the bus does not hang though. I've not analysed the fx of this and if the procedure is exactly it should be.
    iii) as of yesterday evening, MTX was working but logic in my driver was failing to handle sending another MTX to handle the remainder that did not fit into the FIFO. This should have been easy but I hit some multithreading issues which meant that I refactored the interrupt driver today. I will get to try this again soon I hope.

    However, I the meantime I have some actions regarding the I2C and pinmux setup which I will address first on the ticket "AM335x I2C multi-master/slave mode"
  • Paul L said:
    ii) If my state machine is handling a SLV read, but if the I2C peripheral indicates that a SLV write is ongoing (via AAS|XRDY), then I've been giving priority to the bus state- ie. I kill off the slave RD in the driver and move to SLTX mode. Doing this does mean that some SLV rds fail, and need to be re-issued, the bus does not hang though. I've not analysed the fx of this and if the procedure is exactly it should be.

    This would be a good scenario to come up with a reproducible test case and capture what it looks like at the bus level.  I don't quite understand how your state machine thinks you're doing a read, but the peripheral thinks you should be doing a write.  Is that a bug in your state machine?  The peripheral misbehaving?  I think we need to see what's happening on the bus in order to answer that.

    Paul L said:
    However, I the meantime I have some actions regarding the I2C and pinmux setup which I will address first on the ticket "AM335x I2C multi-master/slave mode"

    Yes, mostly I want to sanity check your clocking at the chip level and I2C module level.

  • I have now got the interrupt based driver into better shape, where it's starting to talk to the gateway. I have applied most of the guidelines from Matthijs in the thread e2e.ti.com/.../1961190, who deserves a big thank you for this most useful information! A fraction of the info there I also found myself by gut feel and brute force experimentation, so it’s good that the small portion I found out myself also tallies.

    Shown in the screenshot are the AM335x does a slave TX (000C) followed by a write to the slave, where the AM335x does a slave RX, and our application has received successfully (0x25 2d 00 02 etc...).

    However, the next time the master writes to the slave, on the 2nd byte, we get 0x1518 in STATRAW, which is AAS|XUDF | BF|XRDY | RRDY

    XUDF and XRDY are unexpected here. What I will try tomorrow, is to simply ignore those and see if it’s possible for the RX to proceed regardless, since my driver’s state machine knows we are in RX not TX here.

    I will also look at how to trigger the scope on this occurrence so we can zoom in on the I2C line waveforms, this will hopefully be easier than in the polling driver.

  • Apols, 0x1518 is BB | XUDF | BF|XRDY | RRDY
    Note BB and BF asserted at the same time! In my state machine I am generally ignoring BF, as I find it generally does whatever it pleases, and I cannot rely on it. I am not sure why that is and if there is a way it could behave as intended.
    For slave TX, I am relying on NACK from master, and slave RX look for ARDY in order to know we have finished.
    Perhaps scope traces might give more insight.
  • Paul L said:
    Note BB and BF asserted at the same time!

    BB is a level status (and doesn't really belong in the irq register in the first place), BF is an event that is set on falling edge of BB and must be manually cleared. I've never seen either behave unreliable. (In fact in the lockup state I warned about, BB and BF seemed to be the only parts of the peripheral still working right.)

    Seeing the combination set just means the BB was cleared and then set again before you read status register, or you forgot to clear BF.

    Paul L said:
    0x1518 is BB | XUDF | BF| XRDY | RRDY

    RRDY means the rx fifo is not empty and you are (or should be) still in an RX transaction. Drain the fifo by reading from it until you can clear RRDY. Note that the RRDY bit is sticky so you need to attempt to clear it after reading each byte. Ignore XRDY/XUDF and BB/BF until you've done so. (Perhaps you've actually allowed to deal with a new TX transaction while still draining the RX fifo but I wouldn't take the chance.)

    In contrast XRDY in slave transmitter mode is not sticky but a plain event and should be cleared before writing a byte, to avoid race conditions. Beware that if there's still stale data in the tx fifo when a new slave transaction starts then no XRDY or XUDF will be indicated while it's stalling on SBLOCK. Once you clear the tx fifo and release the stall you get XUDF and know it's a TX transaction. The first XRDY event is lost however in this case.

    You could alternatively ignore XRDY entirely in slave mode and use XUDF only to send a chunk of data (up to fifo size). If the master NACKs prematurely you can check bufstat to see how much was sent (if you care) and clear the fifo before the next transaction. Note that XUDF is sticky if (and only if) enabled as irq and needs to be cleared after writing data (see notes).

     

    The really weird thing in that combination of irq flags is the lack of AAS. Otherwise it strongly suggests you were in an RX transaction, which finished with a stop (-BB, ^BF) after which a slave TX was started (+BB, ^AAS, +XUDF, ^XRDY) and is now being stalled until you release it via SBLOCK.

    In all cases it would suggest your code is occasionally lagging a bit in handling events (possibly due to debug prints?). I don't know anything about QNX really so I'm not sure what your options are to limit how long you can be preempted at any time. Of course with I²C it really ought not matter, both master and slave can stall at any time and as long as they want to (well, until bus partners run out of patience), so with a well-designed I²C peripheral no part of the driver should need to be highly timing-critical. *cough* ...

    Using SBLOCK helps a lot, but a few situations remain:

    1. Changing your set of slave address(es) or exiting the bus as slave. The tricky part is avoid a race condition with the start of a slave transaction. This means checking BB (using BF as notification for it to clear if necessary) and then acting fast.

    2. Reassering SBLOCK after having released a slave transaction from stalling. For peace of mind I'd just do { disable irqs; write ~(1 << index) to SBLOCK; write ~0 to SBLOCK; enable irqs; } or something along those lines. Clear AAS before releasing the transaction to guarantee you can't miss the next one.

    3. General Call. It's just awful how the peripheral handles it. No way to disable (NACK) it and no way to stall it via SBLOCK. If a GC closely follows another slave RX then there's nothing marking the boundary between their data in the rx fifo. Either you'd need to ensure real-time handling of every received byte or take note of tx bufstat [sic] when you get the GC irq, before its first data byte is written, to allow reconstruction of the data boundary. The simplest solution is just prohibiting the use of GC on your i2c bus, if possible.

  • Matthijs van Duin said:
    take note of tx bufstat [sic]

    Sorry, made a thought error there: as my notes show the tx bufstat counter decrements (mod fifo size) for each byte read, hence tells where the read pointer is. In this case you want to know the (internal) write pointer however, for which you need both bufstats. Fortunately they're in a single register anyway.

  • So, I hadn't looked at your original question yet since you managed to fix most problems using my notes, but maybe it's useful for future reference to give a quick "TL;DR" answer of what went wrong originally:

    Paul L said:
    I write another byte to the FIFO, and I clear XRDY. Unfortunately, XRDY bit is not asserted again.

    The XRDY event fired immediately after you wrote to the fifo hence you lost it by clearing afterwards. You probably fell into this trap because in master mode you do need to clear XRDY after write, and clear RRDY after read in either mode.

    Paul L said:
    I am not clearing XUDF, since writing to the FIFO should do that.

    Yes it really ought to. The I²C peripheral however wanted to feel special by having irq behaviour unlike any other :-)

    Paul L said:
    I am not going to clear AAS until ARDY appears

    You may miss AAS events since ARDY doesn't fire if the next transaction has already started by the time you drain the rx fifo.

  • hi Matthijs, thank you again for all the time you've spent here and posting some excellent info that is currently not available elsewhere.

    We have had direct contact with TI supporting us on this too, the Sitara team are looking into the Starterware solution for BBB.

    Much as I hate to leave this unfinished, for business reasons, I need to now pursue another solution which doesn't use the Sitara's I2C block ASAP. So sadly, I need to park this investigation for nowTI-ticket-01Sep(AM335x I2C Slave driver for Qnx).pdf. What I can provide here is pseudcode of my "most good"  version of code. I prepared this prior to getting your latest info today. I did give a quick try to your suggestions, where I  had overlooked them. This was a quick best effort and unfortunately this did not help my driver to work any better, although it's quite likely that any " empirical fudges" in my code to handle unexplained things would need to be removed/reworked. The pdf document describes the code "as is".

    BTW to cover qns above : I had applied changes to the code to clear AAS and keep a memory of having done that - see pseudocode. Also I did get XRDY coming back that comment is now superseded.

    Basically where I got to was (quoting procedure from the other I2C ticket linked above). It got this far, since I was "fudging" to ignore the XRDY that came during slave RX

           - Gateway does a Master READ to device 0 and device 0 reply with a SLAVE WRITE.  (ok - AM335x slave TX)
           - Gateway does a Master WRITE to device 0. (ok - AM335x slave RX)
           - Then device 0 becomes MASTER too.  <-- failed (AM335x master TX) at this point in my "most good" code

    So signs of life, but reliability in this simple exchange was not 100% - and that's before other masters appeared on the bus. So lots more work, and of course don't treat the pseudocode as a reference (I can't give a warranty on something that doesn't work ;-)

    Perhaps TI may be able to provide further guidelines based on the info I have shared with them whilst debugging this.

  • Paul L said:
    for business reasons, I need to now pursue another solution which doesn't use the Sitara's I2C block

    Good to hear! :-)

    Maybe you missed it, but my first advice to your team to abandon the I²C peripheral for multimaster use.

    In fact, after I did the investigation of TI's I²C peripheral a few years ago (which is where my notes came) I actually had the HW guys cut the i2c traces between the mainboard and daughterboard of our prototype and let the soc/mcu on each deal with its local i2c peripherals. (Originally the two would also have communicated with each other via i2c, but fortunately they had the foresight of also including an uart connection.)

    The DM814x I worked on then didn't have PRUSS yet. If I needed reliable multimaster I²C today I would probably consider using that.

    Good luck with your project!

    Matthijs

    PS. BTW, if you ever use multimaster I²C in any context, do mind the fact its arbitration scheme is only reliable between data bits, not e.g. if one master sends a data bit and another a stop condition. These limitations are documented in the I²C specification, but I don't know whether people are generally aware of them. The workaround requires that masters that access the same slave agree on some limitations on message structure (e.g. fixed length, or explicit length prefix in message).