URL
https://opencores.org/ocsvn/openrisc/openrisc/trunk
Subversion Repositories openrisc
[/] [openrisc/] [trunk/] [rtos/] [ecos-3.0/] [packages/] [io/] [i2c/] [current/] [src/] [i2c.cxx] - Rev 819
Go to most recent revision | Compare with Previous | Blame | View Log
//========================================================================== // // i2c.cxx // // Generic API for accessing devices attached to an I2C bus // //========================================================================== // ####ECOSGPLCOPYRIGHTBEGIN#### // ------------------------------------------- // This file is part of eCos, the Embedded Configurable Operating System. // Copyright (C) 2004, 2005, 2009 Free Software Foundation, Inc. // // eCos is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation; either version 2 or (at your option) any later // version. // // eCos is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License // along with eCos; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // As a special exception, if other files instantiate templates or use // macros or inline functions from this file, or you compile this file // and link it with other works to produce a work based on this file, // this file does not by itself cause the resulting work to be covered by // the GNU General Public License. However the source code for this file // must still be made available in accordance with section (3) of the GNU // General Public License v2. // // This exception does not invalidate any other reasons why a work based // on this file might be covered by the GNU General Public License. // ------------------------------------------- // ####ECOSGPLCOPYRIGHTEND#### //========================================================================== //###DESCRIPTIONBEGIN#### // // Author(s): bartv // Date: 2004-10-05 // //###DESCRIPTIONEND#### //======================================================================== #include <pkgconf/infra.h> #include <pkgconf/io_i2c.h> #include <cyg/infra/cyg_ass.h> #include <cyg/infra/cyg_type.h> #include <cyg/infra/diag.h> #include <cyg/io/i2c.h> #include <cyg/hal/hal_io.h> #include <cyg/hal/hal_intr.h> #include <cyg/hal/hal_diag.h> // ---------------------------------------------------------------------------- // Static initialization. // // This is somewhat tricky. There is a strong argument for leaving the // initialization to the platform HAL since with most I2C buses that // code will be straightforward. However that would mean that the // cyg_i2c_bus structure(s) and associated code would always get // linked in, even if the application does not use any i2c devices. // Instead to get the maximum benefit of linker garbage collection // initialization is handled by the generic I2C code, using the usual // dummy C++ object with a prioritized constructor. There is a dummy // references to this object from the transaction end function. The // result should be that if the application uses any I2C functionality // then all required code and data should get included, otherwise it // will all be elided. // // All I2C buses are kept in a table, so that the init code can // iterate through each one. CYG_HAL_TABLE_BEGIN(cyg_i2c_buses, i2c_buses); CYG_HAL_TABLE_END(cyg_i2c_buses_end, i2c_buses); class cyg_i2c_init { public: cyg_uint8 i2c_dummy; cyg_i2c_init(); }; static cyg_i2c_init cyg_i2c_init_object CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_BUS_I2C); cyg_i2c_init::cyg_i2c_init() { cyg_i2c_bus* bus; cyg_i2c_init_object.i2c_dummy = 0; for (bus = &(cyg_i2c_buses[0]); bus != &cyg_i2c_buses_end; bus++) { cyg_drv_mutex_init(&(bus->i2c_lock)); #ifdef CYGDBG_USE_ASSERTS bus->i2c_current_device = (const cyg_i2c_device*) 0; #endif if ((void (*)(cyg_i2c_bus*))0 != bus->i2c_init_fn) { (*bus->i2c_init_fn)(bus); } } } // ---------------------------------------------------------------------------- // The main exported routines just operate in terms of the transaction ones. extern "C" cyg_uint32 cyg_i2c_tx(const cyg_i2c_device* dev, const cyg_uint8* buf, cyg_uint32 count) { cyg_uint32 result; cyg_i2c_transaction_begin(dev); result = cyg_i2c_transaction_tx(dev, true, buf, count, true); cyg_i2c_transaction_end(dev); return result; } extern "C" cyg_uint32 cyg_i2c_rx(const cyg_i2c_device* dev, cyg_uint8* buf, cyg_uint32 count) { cyg_uint32 result; cyg_i2c_transaction_begin(dev); result = cyg_i2c_transaction_rx(dev, true, buf, count, true, true); cyg_i2c_transaction_end(dev); return result; } // Transaction begin/end relate to the per-bus locking. There does not // seem to be any need to interact with the hardware at this point. extern "C" void cyg_i2c_transaction_begin(const cyg_i2c_device* dev) { cyg_i2c_bus* bus; CYG_CHECK_DATA_PTR(dev, "valid I2C device pointer required"); bus = dev->i2c_bus; CYG_CHECK_DATA_PTR(bus, "I2C device does not point at a valid bus structure"); while (! cyg_drv_mutex_lock(&(bus->i2c_lock))); #ifdef CYGDBG_USE_ASSERTS bus->i2c_current_device = dev; #endif // All done, return with the bus locked. } extern "C" cyg_bool cyg_i2c_transaction_begin_nb(const cyg_i2c_device* dev) { cyg_bool result = false; cyg_i2c_bus* bus; CYG_CHECK_DATA_PTR(dev, "valid I2C device pointer required"); bus = dev->i2c_bus; CYG_CHECK_DATA_PTR(bus, "I2C device does not point at a valid bus structure"); if (cyg_drv_mutex_trylock(&(bus->i2c_lock))) { #ifdef CYGDBG_USE_ASSERTS bus->i2c_current_device = dev; #endif result = true; } return result; } extern "C" void cyg_i2c_transaction_end(const cyg_i2c_device* dev) { cyg_i2c_bus* bus; CYG_CHECK_DATA_PTR(dev, "valid I2C device pointer required"); bus = dev->i2c_bus; CYG_CHECK_DATA_PTR(bus, "I2C device does not point at a valid bus structure"); CYG_PRECONDITION(bus->i2c_current_device == dev, "I2C transaction end requested without claiming the bus"); cyg_drv_mutex_unlock(&(bus->i2c_lock)); #ifdef CYGDBG_USE_ASSERTS bus->i2c_current_device = (const cyg_i2c_device*)0; #endif // Avoid problems with linker garbage collection cyg_i2c_init_object.i2c_dummy = 0; } // The I/O operations just indirect through the per-bus function pointers extern "C" cyg_uint32 cyg_i2c_transaction_tx(const cyg_i2c_device* dev, cyg_bool start, const cyg_uint8* buf, cyg_uint32 count, cyg_bool stop) { cyg_i2c_bus* bus; cyg_uint32 result; CYG_CHECK_DATA_PTR(dev, "valid I2C device pointer required"); bus = dev->i2c_bus; CYG_CHECK_DATA_PTR(bus, "I2C device does not point at a valid bus structure"); CYG_PRECONDITION(bus->i2c_current_device == dev, "I2C transaction end requested without claiming the bus"); CYG_CHECK_FUNC_PTR(bus->i2c_tx_fn, "I2C bus has not provided a tx function"); result = (*(bus->i2c_tx_fn))(dev, start, buf, count, stop); return result; } extern "C" cyg_uint32 cyg_i2c_transaction_rx(const cyg_i2c_device* dev, cyg_bool start, cyg_uint8* buf, cyg_uint32 count, cyg_bool nak, cyg_bool stop) { cyg_i2c_bus* bus; cyg_uint32 result; CYG_CHECK_DATA_PTR(dev, "valid I2C device pointer required"); bus = dev->i2c_bus; CYG_CHECK_DATA_PTR(bus, "I2C device does not point at a valid bus structure"); CYG_PRECONDITION(bus->i2c_current_device == dev, "I2C transaction end requested without claiming the bus"); CYG_CHECK_FUNC_PTR(bus->i2c_rx_fn, "I2C bus has not provided an rx function"); result = (*(bus->i2c_rx_fn))(dev, start, buf, count, nak, stop); return result; } extern "C" void cyg_i2c_transaction_stop(const cyg_i2c_device* dev) { cyg_i2c_bus* bus; CYG_CHECK_DATA_PTR(dev, "valid I2C device pointer required"); bus = dev->i2c_bus; CYG_CHECK_DATA_PTR(bus, "I2C device does not point at a valid bus structure"); CYG_PRECONDITION(bus->i2c_current_device == dev, "I2C transaction end requested without claiming the bus"); CYG_CHECK_FUNC_PTR(bus->i2c_stop_fn, "I2C bus has not provided a stop function"); (*(bus->i2c_stop_fn))(dev); } // ---------------------------------------------------------------------------- // The bit-banging support // // Optional debug support, useful while sorting out the platform-specific // bitbang function. #if 1 # define DEBUG(_format_, ...) #else # define DEBUG(_format_, ...) diag_printf(_format_, ## __VA_ARGS__) #endif // Initialization calls into the h/w specific bit-bang function to set // up the GPIO pins and to set both SCL and SDA as outputs and high. extern "C" void cyg_i2c_bitbang_init(cyg_i2c_bus* mash) { cyg_i2c_bitbang_fn banger = (cyg_i2c_bitbang_fn)(mash->i2c_extra); CYG_CHECK_FUNC_PTR(banger, "bitbanged i2c bus has not provided a bitbang function"); DEBUG("i2c bitbang init\n"); (*banger)(mash, CYG_I2C_BITBANG_INIT); } // Send a single byte out of the bus and get back the acknowledgement. // This may be the addressing byte or a data byte. // Preconditions: // SCL output low. The previous operation has completed, the // acknowledgement bit has been received and processed. // None of the devices should be driving the bus. // SDA output, indeterminate value. // Postconditions: // SCL output low, the acknowledgement has been received. // SDA may be left as an input or an output, depending on the // last argument // // The return value is the acknowledge bit, i.e. 0 for ack, 1 for // nak. static cyg_bool cyg_i2c_bitbang_send_byte(cyg_i2c_bus* mash, const cyg_i2c_device* dev, cyg_uint32 delay, cyg_uint8 data, cyg_bool leave_as_input) { cyg_i2c_bitbang_fn banger = (cyg_i2c_bitbang_fn)(mash->i2c_extra); cyg_uint8 mask; cyg_bool result; DEBUG("i2c bitbang send_byte %02x, leave_as_input %d\n", data, leave_as_input); for (mask = 0x0080; 0x00 != mask; ) { // Transfer the next bit of data (*banger)(mash, (0 != (data & mask)) ? CYG_I2C_BITBANG_SDA_HIGH : CYG_I2C_BITBANG_SDA_LOW); mask = mask >> 1; // We should now be able to just set the clock high and wait // for the delay period. However if the device is having // difficulty keeping up then it may perform clock stretching, // basically keeping the clock low for a period of time. // SCL_CLOCKSTRETCH means set the clock high, then wait // until it really has gone high. (*banger)(mash, CYG_I2C_BITBANG_SCL_HIGH_CLOCKSTRETCH); // The device is now busy sampling the bit. HAL_DELAY_US(delay); // Unless this was the last bit, just drop the clock. If it is // the last bit switch SDA to an input as well. (*banger)(mash, (0 != mask) ? CYG_I2C_BITBANG_SCL_LOW : CYG_I2C_BITBANG_SCL_LOW_SDA_INPUT); HAL_DELAY_US(delay); } // The last bit has been sent, SCL is low, and SDA has been turned // into an input. The device should be placing the acknowledge bit // onto SDA. Reading the acknowledge bit still needs a clock cycle. (*banger)(mash, CYG_I2C_BITBANG_SCL_HIGH); HAL_DELAY_US(delay); result = (*banger)(mash, CYG_I2C_BITBANG_SDA_READ); DEBUG("i2c bitbang send_byte, got ack %d\n", result); // Drop SCL again, and leave SDA as either an input or an output // depending on the calling code's requirements and whether or not // the device ack'd. (*banger)(mash, CYG_I2C_BITBANG_SCL_LOW); HAL_DELAY_US(delay); if ((1 == result) || !leave_as_input) { (*banger)(mash, CYG_I2C_BITBANG_SDA_OUTPUT); } return result; } // Read a single byte from the bus and generate an ack/nak. // Preconditions: // SCL output low. The previous operation has completed. // The device should have placed the data on the bus. // SDA input // Postconditions: // SCL output low. The acknowledgement has been sent. // SDA may be left as an input or an output, depending on // whether or not this is the last byte in a transfer. // For the last byte we want to send a nak. // // The result is the byte read. static cyg_uint8 cyg_i2c_bitbang_read_byte(cyg_i2c_bus* mash, const cyg_i2c_device* dev, cyg_uint32 delay, cyg_bool nak) { cyg_i2c_bitbang_fn banger = (cyg_i2c_bitbang_fn)(mash->i2c_extra); cyg_uint32 i; cyg_uint8 result = 0; DEBUG("i2c bitbang read_byte, nak %d\n", nak); for (i = 0; i < 8; i++) { (*banger)(mash, CYG_I2C_BITBANG_SCL_HIGH); HAL_DELAY_US(delay); result = result << 1; if (0 != (*banger)(mash, CYG_I2C_BITBANG_SDA_READ)) { result |= 0x01; } (*banger)(mash, CYG_I2C_BITBANG_SCL_LOW); HAL_DELAY_US(delay); } // We have read the last bit. SDA is still an input, SCL is low. // We need to switch SDA to an output and send the ack/nak (*banger)(mash, CYG_I2C_BITBANG_SDA_OUTPUT); (*banger)(mash, nak ? CYG_I2C_BITBANG_SDA_HIGH : CYG_I2C_BITBANG_SDA_LOW); (*banger)(mash, CYG_I2C_BITBANG_SCL_HIGH); HAL_DELAY_US(delay); (*banger)(mash, nak ? CYG_I2C_BITBANG_SCL_LOW : CYG_I2C_BITBANG_SCL_LOW_SDA_INPUT); HAL_DELAY_US(delay); DEBUG("i2c bitbang read_byte, result %02x\n", result); return result; } // Generate a start condition. // // Preconditions: // SCL output, indeterminate // SDA output, indeterminate // Postconditions: // SCL output low // SDA output low // // At the start of a transaction we know that both SCL and SDA will be // high, but for a repeated start static void cyg_i2c_bitbang_send_start(cyg_i2c_bus* mash, cyg_uint32 delay) { cyg_i2c_bitbang_fn banger = (cyg_i2c_bitbang_fn)(mash->i2c_extra); DEBUG("i2c bitbang, generating start\n"); // First get both SCL and SDA back into a known state (*banger)(mash, CYG_I2C_BITBANG_SDA_HIGH); (*banger)(mash, CYG_I2C_BITBANG_SCL_HIGH); HAL_DELAY_US(delay); // A start condition involves dropping SDA while SCL stays high. (*banger)(mash, CYG_I2C_BITBANG_SDA_LOW); // Make sure that all devices have seen the start condition and // reacted HAL_DELAY_US(delay); // Drop the clock, so that we can send the address/direction byte (*banger)(mash, CYG_I2C_BITBANG_SCL_LOW); HAL_DELAY_US(delay); } // Generate a stop condition // Preconditions: // SCL output, low // SDA output, indeterminate // Postconditions: // SCL output, high // SDA output, high static void cyg_i2c_bitbang_send_stop(cyg_i2c_bus* mash, cyg_uint32 delay) { cyg_i2c_bitbang_fn banger = (cyg_i2c_bitbang_fn)(mash->i2c_extra); DEBUG("i2c bitbang, generating stop\n"); // We need SDA low, then we can generate the stop signal (*banger)(mash, CYG_I2C_BITBANG_SDA_LOW); (*banger)(mash, CYG_I2C_BITBANG_SCL_HIGH); HAL_DELAY_US(delay); (*banger)(mash, CYG_I2C_BITBANG_SDA_HIGH); // Ensure that the minimum delay between stop and start is observed HAL_DELAY_US(delay); } // A full transfer to a given device, within in a transaction. extern "C" cyg_uint32 cyg_i2c_bitbang_tx(const cyg_i2c_device* dev, cyg_bool send_start, const cyg_uint8* buf, cyg_uint32 count, cyg_bool send_stop) { cyg_uint32 result = 0; // The bit-bang code works in terms of us delays rather than ns cyg_uint32 delay = (0 == dev->i2c_delay) ? 1 : (dev->i2c_delay + 1999) / 2000; CYG_CHECK_DATA_PTR(buf, "i2c tx operation requested but no data supplied"); CYG_PRECONDITION(count > 0, "at least one byte should be sent"); DEBUG("i2c bitbang tx, dev %08x, addr %02x, delay %d, send_start %d, buf %08x, count %d, send_stop %d\n", dev, dev->i2c_address, delay, send_start, buf, count, send_stop); if (send_start) { cyg_i2c_bitbang_send_start(dev->i2c_bus, delay); // Now send a single byte, holding the address shifted left and a // 0 to indicate a write. A nak indicates that the device has not // responded. SDA should stay as an output. if (0 != cyg_i2c_bitbang_send_byte(dev->i2c_bus, dev, delay, (dev->i2c_address << 1) | 0x00, false) ) { // Get the bus back in a consistent state DEBUG("i2 bitbang tx, no device has responded to address %02x\n", dev->i2c_address); cyg_i2c_bitbang_send_stop(dev->i2c_bus, delay); return 0; } // The device has ack'd so we can continue sending } // Send all bytes, unless we receive a nak. SDA should remain an output do { result += 1; if (0 != cyg_i2c_bitbang_send_byte(dev->i2c_bus, dev, delay, buf[result - 1], false)) { break; } } while (result < count); // At this point we have SCL low and SDA an indeterminate output if (send_stop) { cyg_i2c_bitbang_send_stop(dev->i2c_bus, delay); } DEBUG("i2c bitbang tx, %d bytes sent out of %d\n", result, count); return result; } // A full transfer from a given device. The bus has already been locked // by higher-level code. On entry both SDA and SCL should be outputs // and both should be high. The same conditions should hold on exit. extern "C" cyg_uint32 cyg_i2c_bitbang_rx(const cyg_i2c_device* dev, cyg_bool send_start, cyg_uint8* buf, cyg_uint32 count, cyg_bool send_nak, cyg_bool send_stop) { cyg_uint32 result = 0; // The bit-bang code works in terms of us delays rather than ns cyg_uint32 delay = (0 == dev->i2c_delay) ? 1 : (dev->i2c_delay + 1999) / 2000; DEBUG("i2c bitbang rx, dev %08x, addr %02x, delay %d, send_start %d, buf %08x, count %d, send_nak %d, send_stop %d\n", dev, dev->i2c_address, delay, send_start, buf, count, send_nak, send_stop); CYG_CHECK_DATA_PTR(buf, "i2c rx operation requested but no data supplied"); CYG_PRECONDITION(count > 0, "at least one byte should be sent"); CYG_PRECONDITION(send_nak || !send_stop, "a stop can only be generated after the receive has been nak'd"); if (send_start) { cyg_i2c_bitbang_send_start(dev->i2c_bus, delay); // Now send a single byte, holding the address shifted left and a // 1 to indicate a read. A nak indicates that the device has not // responded. SDA should become an output. if (0 != cyg_i2c_bitbang_send_byte(dev->i2c_bus, dev, delay, (dev->i2c_address << 1) | 0x01, true) ) { // Get the bus back in a consistent state DEBUG("i2c bitbang rx, no device has responded to address %02x\n", dev->i2c_address); cyg_i2c_bitbang_send_stop(dev->i2c_bus, delay); return 0; } // The device has ack'd so we can continue sending } // The device has ack'd. Read the number of bytes specified. The // device cannot stop sending, although it may clock-stretch. For // the last byte we usually we want to NAK and SDA should become // an input, but if the data stream is variable length with e.g. // the first byte indicating the length then we want to support // partial reads. for (result = 0; result < count; result++) { buf[result] = cyg_i2c_bitbang_read_byte(dev->i2c_bus, dev, delay, (send_nak && (result == (count - 1))) ? true : false); } // At this point we have SCL low and SDA an indeterminate output if (send_stop) { cyg_i2c_bitbang_send_stop(dev->i2c_bus, delay); } DEBUG("i2c bitbang rx, read %d bytes\n", result); return result; } extern "C" void cyg_i2c_bitbang_stop(const cyg_i2c_device* dev) { // The bit-bang code works in terms of us delays rather than ns cyg_uint32 delay = (0 == dev->i2c_delay) ? 1 : (dev->i2c_delay + 1999) / 2000; cyg_i2c_bitbang_send_stop(dev->i2c_bus, delay); }
Go to most recent revision | Compare with Previous | Blame | View Log