# HG changeset patch # User Paul Boddie # Date 1613512411 -3600 # Node ID 55b925ad06ef02f2ed75a3fac64d9d2cbe2597fa # Parent b0145ae94a490f296839182dc14beb523d318985 Demonstrate use of a state machine in the Letux 400 I2C driver. diff -r b0145ae94a49 -r 55b925ad06ef pkg/devices/lib/i2c/include/i2c-jz4730.h --- a/pkg/devices/lib/i2c/include/i2c-jz4730.h Sun Jan 31 22:47:14 2021 +0100 +++ b/pkg/devices/lib/i2c/include/i2c-jz4730.h Tue Feb 16 22:53:31 2021 +0100 @@ -31,6 +31,22 @@ #include #include + + +// State machine states. + +enum I2c_jz4730_state +{ + I2c_jz4730_end = 0, + I2c_jz4730_pre_start, + I2c_jz4730_start_read, + I2c_jz4730_perform_read, + I2c_jz4730_perform_write, + I2c_jz4730_stop_write +}; + + + // I2C channel. class I2c_jz4730_channel @@ -54,6 +70,21 @@ protected: void set_frequency(); + // State machine activities. + + unsigned int _nread, _nwritten, _length, _limit; + uint8_t _address; + uint8_t *_buf; + bool _read; + + void communicate(); + + enum I2c_jz4730_state pre_start(); + enum I2c_jz4730_state start_read(); + enum I2c_jz4730_state perform_read(); + enum I2c_jz4730_state perform_write(); + enum I2c_jz4730_state stop_write(); + // Transaction control. bool set_address(uint8_t address, bool read); diff -r b0145ae94a49 -r 55b925ad06ef pkg/devices/lib/i2c/src/jz4730.cc --- a/pkg/devices/lib/i2c/src/jz4730.cc Sun Jan 31 22:47:14 2021 +0100 +++ b/pkg/devices/lib/i2c/src/jz4730.cc Tue Feb 16 22:53:31 2021 +0100 @@ -133,31 +133,192 @@ _regs[I2c_clock] = division; } -// Present the address on the bus. +// State machine controller. -bool -I2c_jz4730_channel::set_address(uint8_t address, bool read) +void +I2c_jz4730_channel::communicate() { - // Waiting for long enough may eliminate a busy condition and thus permit a - // new transaction. 10ms appears to be long enough, whereas 1ms does not - // appear to be. In case this is insufficient, permit failure. - - unsigned int limit = 10; + enum I2c_jz4730_state state = I2c_jz4730_pre_start; + _limit = 10; do { - if (!wait_for_irq(1000) && !(--limit)) - return false; + wait_for_irq(1000); + + switch (state) + { + case I2c_jz4730_pre_start: + state = pre_start(); + break; + + case I2c_jz4730_start_read: + state = start_read(); + break; + + case I2c_jz4730_perform_read: + state = perform_read(); + break; + + case I2c_jz4730_perform_write: + state = perform_write(); + break; + + case I2c_jz4730_stop_write: + state = stop_write(); + break; + + default: + break; + } } - while (busy()); + while (state != I2c_jz4730_end); +} + +// State machine implementation handlers. + +// Assert not busy state, issue start, present the address on the bus. + +enum I2c_jz4730_state +I2c_jz4730_channel::pre_start() +{ + // Wait again if busy up to the limit. + + if (busy()) + { + if (!(--_limit)) + return I2c_jz4730_end; + else + return I2c_jz4730_pre_start; + } + + // Use a longer time limit in subsequent activities. + + _limit = 1000; + + // Start, send address, proceed to the operation. start(); - _regs[I2c_data] = (address << 1) | (read ? 1 : 0); + _regs[I2c_data] = (_address << 1) | (_read ? 1 : 0); send_next(); - return true; + return _read ? I2c_jz4730_start_read : I2c_jz4730_perform_write; +} + +// Wait for an opportunity to begin reading. + +enum I2c_jz4730_state +I2c_jz4730_channel::start_read() +{ + // Wait again if not ready to read. + + if (transferring() || (!data_valid() && !nack())) + return I2c_jz4730_start_read; + + return I2c_jz4730_perform_read; +} + +// Attempt to read from the device. + +enum I2c_jz4730_state +I2c_jz4730_channel::perform_read() +{ + // Wait again if no available data. + + if (!data_valid() && !nack()) + { + if (!(--_limit)) + { + stop(); + return I2c_jz4730_end; + } + else + return I2c_jz4730_perform_read; + } + + // Stop if NACK received. + + if (nack()) + { + stop(); + return I2c_jz4730_end; + } + + // Signal last byte if appropriate. + + if ((!_nread && (_length == 1)) || (_nread == _length - 2)) + signal_last(); + + // Store and solicit data. + + _buf[_nread++] = _regs[I2c_data]; + clear_next(); + + // Stop if all data received. + + if (_nread >= _length) + { + stop(); + return I2c_jz4730_end; + } + + // Wait for more data otherwise. + + _limit = 1000; + return I2c_jz4730_perform_read; +} + +// Attempt to write to the device. + +enum I2c_jz4730_state +I2c_jz4730_channel::perform_write() +{ + // Wait for data (address or previous data) to be sent. + + if (data_valid() && !nack()) + { + if (!(--_limit)) + { + stop(); + return I2c_jz4730_end; + } + else + return I2c_jz4730_perform_write; + } + + // Stop if all data written or NACK received. + + if ((_nwritten >= _length) || nack()) + { + stop(); + _limit = 1000; + return I2c_jz4730_stop_write; + } + + // Write more data. + + _regs[I2c_data] = _buf[_nwritten++]; + send_next(); + + // Wait for the data to be sent. + + _limit = 1000; + return I2c_jz4730_perform_write; +} + +// Terminate the write transaction. + +enum I2c_jz4730_state +I2c_jz4730_channel::stop_write() +{ + if (!transferred()) + { + if (--_limit) + return I2c_jz4730_stop_write; + } + + return I2c_jz4730_end; } // Wait up to the given timeout (in microseconds) for an interrupt request, @@ -174,54 +335,15 @@ unsigned int I2c_jz4730_channel::read(uint8_t address, uint8_t buf[], unsigned int length) { - unsigned int nread = 0; - - if (!set_address(address, true)) - return 0; - - // Wait for an opportunity to begin reading. - - do - { - if (!wait_for_irq(1000000)) - printf("start (no irq): status = %x\n", (uint32_t) _regs[I2c_status]); - } - while (transferring() || (!data_valid() && !nack())); - - // Device apparently unavailable. - - if (nack()) - { - stop(); - return nread; - } - - // Attempt to read from the device. + _nread = 0; + _length = length; + _address = address; + _buf = &buf[0]; + _read = true; - while (nread < length) - { - do - { - if (!wait_for_irq(1000000)) - { - stop(); - return nread; - } - } - while (!data_valid() && !nack()); + communicate(); - if (nack()) - break; - - if ((!nread && (length == 1)) || (nread == length - 2)) - signal_last(); - - buf[nread++] = _regs[I2c_data]; - clear_next(); - } - - stop(); - return nread; + return _nread; } // Write data to the bus. @@ -229,49 +351,15 @@ unsigned int I2c_jz4730_channel::write(uint8_t address, uint8_t buf[], unsigned int length) { - unsigned int nwritten = 0; - - if (!set_address(address, false)) - return 0; - - do - { - if (!wait_for_irq(1000000)) - { - printf("write (no irq): status = %x\n", (uint32_t) _regs[I2c_status]); - stop(); - return nwritten; - } - } - while (data_valid() && !nack()); - - while ((nwritten < length) && !nack()) - { - _regs[I2c_data] = buf[nwritten++]; - send_next(); + _nwritten = 0; + _length = length; + _address = address; + _buf = &buf[0]; + _read = false; - do - { - if (!wait_for_irq(1000000)) - { - printf("write (no irq): status = %x\n", (uint32_t) _regs[I2c_status]); - stop(); - return nwritten; - } - } - while (data_valid() && !nack()); - } + communicate(); - stop(); - - do - { - if (!wait_for_irq(1000000)) - break; - } - while (!transferred()); - - return nwritten; + return _nwritten; } // Test for data validity.