diff options
Diffstat (limited to 'ecos/packages/devs/i2c/arm/lpc2xxx/current/src/i2c_lpc2xxx.c')
-rw-r--r-- | ecos/packages/devs/i2c/arm/lpc2xxx/current/src/i2c_lpc2xxx.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/ecos/packages/devs/i2c/arm/lpc2xxx/current/src/i2c_lpc2xxx.c b/ecos/packages/devs/i2c/arm/lpc2xxx/current/src/i2c_lpc2xxx.c new file mode 100644 index 0000000..fff6a3a --- /dev/null +++ b/ecos/packages/devs/i2c/arm/lpc2xxx/current/src/i2c_lpc2xxx.c @@ -0,0 +1,491 @@ +//========================================================================== +// +// i2c_lpc2xxx.c +// +// I2C driver for LPC2xxx +// +//========================================================================== +// ####ECOSGPLCOPYRIGHTBEGIN#### +// ------------------------------------------- +// This file is part of eCos, the Embedded Configurable Operating System. +// Copyright (C) 2008 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): Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org> +// Contributors: Uwe Kindler <uwe_kindler@web.de> +// Date: 2007-07-12 +// Purpose: +// Description: +// +//####DESCRIPTIONEND#### +// +//========================================================================== + + +//========================================================================== +// INCLUDES +//========================================================================== +#include <pkgconf/system.h> +#include <pkgconf/devs_i2c_arm_lpc2xxx.h> + +#include <cyg/infra/cyg_type.h> +#include <cyg/infra/cyg_ass.h> +#include <cyg/infra/diag.h> +#include <cyg/io/i2c.h> +#include <cyg/io/i2c_lpc2xxx.h> +#include <cyg/hal/hal_arch.h> +#include <cyg/hal/hal_io.h> +#include <cyg/hal/hal_intr.h> +#include <cyg/hal/drv_api.h> + +// +// According to the Users Manual the LPC2xxx I2C module is very +// similar to the I2C module of the Philips 8xC552/556 controllers. I +// guess it is used in other Philips/NXP controllers, too. Using these +// macros should make it easier to split off the common parts of the +// driver once it's necessary. +// +// Optimize for the case of a single bus device, while still allowing +// multiple devices. +// +#ifndef CYGHWR_DEVS_I2C_ARM_LPC2XXX_MULTIPLE_BUSES +# define I2C_BASE(_extra_) (cyg_uint8*)HAL_LPC2XXX_I2C_SINGLETON_BASE +# define I2C_ISRVEC(_extra_) HAL_LPC2XXX_I2C_SINGLETON_ISRVEC +# define I2C_ISRPRI(_extra_) HAL_LPC2XXX_I2C_SINGLETON_ISRPRI +# define I2C_CLK(_extra_) HAL_LPC2XXX_I2C_SINGLETON_CLK +# define I2C_BUS_FREQ(_extra_) HAL_LPC2XXX_I2C_SINGLETON_BUS_FREQ +#else +# define I2C_BASE(_extra_) ((_extra_)->i2c_base) +# define I2C_ISRVEC(_extra_) ((_extra_)->i2c_isrvec) +# define I2C_ISRPRI(_extra_) ((_extra_)->i2c_isrpri) +# define I2C_CLK(_extra_) ((_extra_)->i2c_pclk) +# define I2C_BUS_FREQ(_extra_) ((_extra_)->i2c_bus_freq) +#endif // CYGHWR_DEVS_I2C_ARM_LPC2XXX_MULTIPLE_BUSES + +#define I2C_XFER 8 + +#define I2C_CONSET(_extra_) (I2C_BASE(_extra_) + 0x0000) +#define I2C_CON(_extra_) I2C_CONSET(_extra_) +#define I2C_STAT(_extra_) (I2C_BASE(_extra_) + 0x0004) +#define I2C_DAT(_extra_) (I2C_BASE(_extra_) + 0x0008) +#define I2C_ADR(_extra_) (I2C_BASE(_extra_) + 0x000C) +#define I2C_SCLH(_extra_) (I2C_BASE(_extra_) + 0x0010) +#define I2C_SCLL(_extra_) (I2C_BASE(_extra_) + 0x0014) +#define I2C_CONCLR(_extra_) (I2C_BASE(_extra_) + 0x0018) + +#define I2C_R8(r, x) HAL_READ_UINT8 ((r), (x)) +#define I2C_W8(r, x) HAL_WRITE_UINT8 ((r), (x)) +#define I2C_R16(r, x) HAL_READ_UINT16 ((r), (x)) +#define I2C_W16(r, x) HAL_WRITE_UINT16((r), (x)) + +// Special case for setting/clearing bits in I2C_CON +#define SET_CON(_extra_, x) I2C_W8(I2C_CONSET(_extra_), (x)) +#define CLR_CON(_extra_, x) I2C_W8(I2C_CONCLR(_extra_), (x)) + +// I2C_CONSET register bits +#define CON_AA (1<<2) +#define CON_SI (1<<3) +#define CON_STO (1<<4) +#define CON_STA (1<<5) +#define CON_EN (1<<6) + + +#define I2C_FLAG_FINISH 1 // transfer finished +#define I2C_FLAG_ACT 2 // bus still active, no STOP condition send +#define I2C_FLAG_ERROR (1<<31) // one of the following errors occured: +#define I2C_FLAG_ADDR (1<<30) // - address was not ACKed +#define I2C_FLAG_DATA (1<<29) // - data was not ACKed +#define I2C_FLAG_LOST (1<<28) // - bus arbitration was lost +#define I2C_FLAG_BUF (1<<27) // - no buffer for reading or writing +#define I2C_FLAG_UNK (1<<26) // - unknown I2C status +#define I2C_FLAG_BUS (1<<25) // - bus error + +#if CYGPKG_DEVS_I2C_ARM_LPC2XXX_DEBUG_LEVEL > 0 + #define debug1_printf(args...) diag_printf(args) +#else + #define debug1_printf(args...) +#endif +#if CYGPKG_DEVS_I2C_ARM_LPC2XXX_DEBUG_LEVEL > 1 + #define debug2_printf(args...) diag_printf(args) +#else + #define debug2_printf(args...) +#endif + +//========================================================================== +// The ISR does the actual work. It is not that much work to justify +// putting it in the DSR, and it is also not clear whether this would +// even work. If an error occurs we try to leave the bus in the same +// state as we would if there was no error. +//========================================================================== +static cyg_uint32 lpc2xxx_i2c_isr(cyg_vector_t vec, cyg_addrword_t data) +{ + cyg_lpc2xxx_i2c_extra* extra = (cyg_lpc2xxx_i2c_extra*)data; + cyg_uint8 status; + + I2C_R8(I2C_STAT(extra), status); + switch(status) + { + case 0x00: // bus error, stop transfer + SET_CON(extra, CON_STO); + extra->i2c_flag = I2C_FLAG_ERROR | I2C_FLAG_BUS; + break; + + case 0x08: // START sent, send Addr+R/W + case 0x10: // ReSTART sent, send Addr+R/W + CLR_CON(extra, CON_STA); + I2C_W8(I2C_DAT(extra), extra->i2c_addr); + break; + + case 0x18: // Addr ACKed, send data + if(extra->i2c_txbuf == NULL) + { + extra->i2c_flag = I2C_FLAG_ERROR | I2C_FLAG_BUF; + cyg_drv_interrupt_mask_intunsafe(vec); + cyg_drv_interrupt_acknowledge(vec); + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; + } + I2C_W8(I2C_DAT(extra), *extra->i2c_txbuf); + extra->i2c_txbuf++; + break; + + case 0x28: // Data ACKed, send more + extra->i2c_count--; + if(extra->i2c_count == 0) + { + extra->i2c_flag = I2C_FLAG_FINISH; + cyg_drv_interrupt_mask_intunsafe(vec); + cyg_drv_interrupt_acknowledge(vec); + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; + } + I2C_W8(I2C_DAT(extra), *extra->i2c_txbuf); + extra->i2c_txbuf++; + break; + + case 0x50: // Data ACKed, receive more + case 0x58: // Data not ACKed, end reception + if(extra->i2c_rxbuf == NULL) + { + extra->i2c_flag = I2C_FLAG_ERROR | I2C_FLAG_BUF; + cyg_drv_interrupt_mask_intunsafe(vec); + cyg_drv_interrupt_acknowledge(vec); + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; + } + + I2C_R8(I2C_DAT(extra), *extra->i2c_rxbuf); + extra->i2c_rxbuf++; + extra->i2c_count--; + // fall through + + case 0x40: // Addr ACKed, receive data + if(status == 0x58 || extra->i2c_count == 0) + { + extra->i2c_flag = I2C_FLAG_FINISH; + cyg_drv_interrupt_mask_intunsafe(vec); + cyg_drv_interrupt_acknowledge(vec); + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; + } + + if((extra->i2c_count == 1) && extra->i2c_rxnak) + { + CLR_CON(extra, CON_AA); + } + else + { + SET_CON(extra, CON_AA); + } + break; + + case 0x20: // Addr not ACKed + case 0x48: // Addr not ACKed + SET_CON(extra, CON_STO); // tranfer failed - force stop + extra->i2c_flag = I2C_FLAG_ERROR | I2C_FLAG_ADDR; + cyg_drv_interrupt_mask_intunsafe(vec); + cyg_drv_interrupt_acknowledge(vec); + break; + + case 0x30: // Data not ACKed + SET_CON(extra, CON_STO); // tranfer failed - force stop + extra->i2c_count++; + extra->i2c_txbuf--; + extra->i2c_flag = I2C_FLAG_ERROR | I2C_FLAG_DATA; + cyg_drv_interrupt_mask_intunsafe(vec); + cyg_drv_interrupt_acknowledge(vec); + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; + break; + + case 0x38: // Arbitration lost + extra->i2c_flag = I2C_FLAG_ERROR | I2C_FLAG_LOST; + break; + + default: // lots of unused states + extra->i2c_flag = I2C_FLAG_ERROR | I2C_FLAG_UNK; + break; + } // switch(status) + + CLR_CON(extra, CON_SI); + cyg_drv_interrupt_acknowledge(vec); + + // + // We need to call the DSR only if there is really something to signal, + // that means only if extra->i2c_flag != 0 + // + if (extra->i2c_flag) + { + return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR; + } + else + { + return CYG_ISR_HANDLED; + } +} + + +//========================================================================== +// DSR signals data +//========================================================================== +static void +lpc2xxx_i2c_dsr(cyg_vector_t vec, cyg_ucount32 count, cyg_addrword_t data) +{ + cyg_lpc2xxx_i2c_extra* extra = (cyg_lpc2xxx_i2c_extra*)data; + if(extra->i2c_flag) + { + cyg_drv_cond_signal(&extra->i2c_wait); + } +} + + +//========================================================================== +// Initialize driver & hardware state +//========================================================================== +void cyg_lpc2xxx_i2c_init(struct cyg_i2c_bus *bus) +{ + cyg_lpc2xxx_i2c_extra* extra = (cyg_lpc2xxx_i2c_extra*)bus->i2c_extra; + cyg_uint16 duty_cycle; + + cyg_drv_mutex_init(&extra->i2c_lock); + cyg_drv_cond_init(&extra->i2c_wait, &extra->i2c_lock); + cyg_drv_interrupt_create(I2C_ISRVEC(extra), + I2C_ISRPRI(extra), + (cyg_addrword_t) extra, + &lpc2xxx_i2c_isr, + &lpc2xxx_i2c_dsr, + &(extra->i2c_interrupt_handle), + &(extra->i2c_interrupt_data)); + cyg_drv_interrupt_attach(extra->i2c_interrupt_handle); + + + CLR_CON(extra, CON_EN | CON_STA | CON_SI | CON_AA); + HAL_WRITE_UINT8(I2C_ADR(extra), 0); + + // + // Setup I2C bus frequency + // + duty_cycle = (I2C_CLK(extra) / I2C_BUS_FREQ(extra)) / 2; + HAL_WRITE_UINT16(I2C_SCLL(extra), duty_cycle); + HAL_WRITE_UINT16(I2C_SCLH(extra), duty_cycle); + + SET_CON(extra, CON_EN); +} + + +//========================================================================== +// transmit a buffer to a device +//========================================================================== +cyg_uint32 cyg_lpc2xxx_i2c_tx(const cyg_i2c_device *dev, + cyg_bool send_start, + const cyg_uint8 *tx_data, + cyg_uint32 count, + cyg_bool send_stop) +{ + cyg_lpc2xxx_i2c_extra* extra = + (cyg_lpc2xxx_i2c_extra*)dev->i2c_bus->i2c_extra; + extra->i2c_addr = dev->i2c_address << 1; + extra->i2c_count = count; + extra->i2c_txbuf = tx_data; + + // + // for a repeated start the SI bit has to be reset + // if we continue a previous transfer, load the next byte + // + if(send_start) + { + SET_CON(extra, CON_STA); + if (I2C_FLAG_ACT == extra->i2c_flag) + { + CLR_CON(extra, CON_SI); + } + } + else + { + HAL_WRITE_UINT8(I2C_DAT(extra), *(extra->i2c_txbuf)); + extra->i2c_txbuf++; + CLR_CON(extra, CON_SI); + } + + extra->i2c_flag = 0; + + // + // the isr will do most of the work, and the dsr will signal when an + // error occured or the transfer finished + // + cyg_drv_mutex_lock(&extra->i2c_lock); + cyg_drv_dsr_lock(); + cyg_drv_interrupt_unmask(I2C_ISRVEC(extra)); + while(!(extra->i2c_flag & (I2C_FLAG_FINISH | I2C_FLAG_ERROR))) + { + cyg_drv_cond_wait(&extra->i2c_wait); + } + cyg_drv_interrupt_mask(I2C_ISRVEC(extra)); + cyg_drv_dsr_unlock(); + cyg_drv_mutex_unlock(&extra->i2c_lock); + + // too bad we have no way to tell the caller + if(extra->i2c_flag & I2C_FLAG_ERROR) + { + debug1_printf("I2C TX error flag: %x\n", extra->i2c_flag); + extra->i2c_flag = 0; + } + else + { + if(send_stop) + { + SET_CON(extra, CON_STO); + CLR_CON(extra, CON_SI | CON_STA); + extra->i2c_flag = 0; + } + else + { + extra->i2c_flag = I2C_FLAG_ACT; + } + } + + count -= extra->i2c_count; + + extra->i2c_addr = 0; + extra->i2c_count = 0; + extra->i2c_txbuf = NULL; + + return count; +} + + +//========================================================================== +// receive into a buffer from a device +//========================================================================== +cyg_uint32 cyg_lpc2xxx_i2c_rx(const cyg_i2c_device *dev, + cyg_bool send_start, + cyg_uint8 *rx_data, + cyg_uint32 count, + cyg_bool send_nak, + cyg_bool send_stop) +{ + cyg_lpc2xxx_i2c_extra* extra = + (cyg_lpc2xxx_i2c_extra*)dev->i2c_bus->i2c_extra; + extra->i2c_addr = (dev->i2c_address << 1) | 0x01; + extra->i2c_count = count; + extra->i2c_rxbuf = rx_data; + extra->i2c_rxnak = send_nak; + + // + // for a repeated start the SI bit has to be reset + // if we continue a previous transfer, start reception + // + if(send_start) + { + SET_CON(extra, CON_STA); + if (I2C_FLAG_ACT == extra->i2c_flag) + { + CLR_CON(extra, CON_SI); + } + } + + extra->i2c_flag = 0; + + // + // the isr will do most of the work, and the dsr will signal when an + // error occurred or the transfer finished + // + cyg_drv_mutex_lock(&extra->i2c_lock); + cyg_drv_dsr_lock(); + cyg_drv_interrupt_unmask(I2C_ISRVEC(extra)); + while(!(extra->i2c_flag & (I2C_FLAG_FINISH | I2C_FLAG_ERROR))) + { + cyg_drv_cond_wait(&extra->i2c_wait); + } + cyg_drv_interrupt_mask(I2C_ISRVEC(extra)); + cyg_drv_dsr_unlock(); + cyg_drv_mutex_unlock(&extra->i2c_lock); + + // too bad we have no way to tell the caller + if (extra->i2c_flag & I2C_FLAG_ERROR) + { + diag_printf("I2C RX error flag: %x\n", extra->i2c_flag); + extra->i2c_flag = 0; + } + else + { + if(send_stop) + { + SET_CON(extra, CON_STO); + CLR_CON(extra, CON_SI | CON_STA); + extra->i2c_flag = 0; + } + else + { + extra->i2c_flag = I2C_FLAG_ACT; + } + } + + count -= extra->i2c_count; + + extra->i2c_addr = 0; + extra->i2c_count = 0; + extra->i2c_rxbuf = NULL; + + return count; +} + + +//========================================================================== +// generate a STOP +//========================================================================== +void cyg_lpc2xxx_i2c_stop(const cyg_i2c_device *dev) +{ + cyg_lpc2xxx_i2c_extra* extra = + (cyg_lpc2xxx_i2c_extra*)dev->i2c_bus->i2c_extra; + extra = extra; // avoid compiler warning in case of singleton + SET_CON(extra, CON_STO); + extra->i2c_flag = 0; + extra->i2c_count = 0; +} + +//--------------------------------------------------------------------------- +// eof i2c_lpc2xxx.c |