/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup lpc32xx_i2c
*
* @brief I2C support implementation.
*/
/*
* Copyright (c) 2010-2011 embedded brains GmbH. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <rtems.h>
#include <bsp.h>
#include <bsp/i2c.h>
void lpc32xx_i2c_reset(volatile lpc32xx_i2c *i2c)
{
i2c->ctrl = I2C_CTRL_RESET;
}
rtems_status_code lpc32xx_i2c_init(
volatile lpc32xx_i2c *i2c,
unsigned clock_in_hz
)
{
uint32_t i2cclk = 0;
if (i2c == &lpc32xx.i2c_1) {
i2cclk |= I2CCLK_1_EN | I2CCLK_1_HIGH_DRIVE;
} else if (i2c == &lpc32xx.i2c_2) {
i2cclk |= I2CCLK_2_EN | I2CCLK_2_HIGH_DRIVE;
} else {
return RTEMS_INVALID_ID;
}
LPC32XX_I2CCLK_CTRL |= i2cclk;
lpc32xx_i2c_reset(i2c);
return lpc32xx_i2c_clock(i2c, clock_in_hz);
}
rtems_status_code lpc32xx_i2c_clock(
volatile lpc32xx_i2c *i2c,
unsigned clock_in_hz
)
{
uint32_t clk_div = lpc32xx_hclk() / clock_in_hz;
uint32_t clk_lo = 0;
uint32_t clk_hi = 0;
switch (clock_in_hz) {
case 100000:
clk_lo = clk_div / 2;
break;
case 400000:
clk_lo = (64 * clk_div) / 100;
break;
default:
return RTEMS_INVALID_CLOCK;
}
clk_hi = clk_div - clk_lo;
i2c->clk_lo = clk_lo;
i2c->clk_hi = clk_hi;
return RTEMS_SUCCESSFUL;
}
static rtems_status_code wait_for_transaction_done(volatile lpc32xx_i2c *i2c)
{
uint32_t stat = 0;
do {
stat = i2c->stat;
} while ((stat & I2C_STAT_TDI) == 0);
if ((stat & I2C_STAT_TFE) != 0) {
i2c->stat = I2C_STAT_TDI;
return RTEMS_SUCCESSFUL;
} else {
lpc32xx_i2c_reset(i2c);
return RTEMS_IO_ERROR;
}
}
static rtems_status_code tx(volatile lpc32xx_i2c *i2c, uint32_t data)
{
uint32_t stat = 0;
do {
stat = i2c->stat;
} while ((stat & (I2C_STAT_TFE | I2C_STAT_TDI)) == 0);
if ((stat & I2C_STAT_TDI) == 0) {
i2c->rx_or_tx = data;
return RTEMS_SUCCESSFUL;
} else {
lpc32xx_i2c_reset(i2c);
return RTEMS_IO_ERROR;
}
}
rtems_status_code lpc32xx_i2c_write_start(
volatile lpc32xx_i2c *i2c,
unsigned addr
)
{
return tx(i2c, I2C_TX_ADDR(addr) | I2C_TX_START);
}
rtems_status_code lpc32xx_i2c_read_start(
volatile lpc32xx_i2c *i2c,
unsigned addr
)
{
return tx(i2c, I2C_TX_ADDR(addr) | I2C_TX_START | I2C_TX_READ);
}
rtems_status_code lpc32xx_i2c_write_with_optional_stop(
volatile lpc32xx_i2c *i2c,
const uint8_t *out,
size_t n,
bool stop
)
{
rtems_status_code sc = RTEMS_SUCCESSFUL;
size_t i = 0;
for (i = 0; i < n - 1 && sc == RTEMS_SUCCESSFUL; ++i) {
sc = tx(i2c, out [i]);
}
if (sc == RTEMS_SUCCESSFUL) {
uint32_t stop_flag = stop ? I2C_TX_STOP : 0;
sc = tx(i2c, out [n - 1] | stop_flag);
}
if (stop && sc == RTEMS_SUCCESSFUL) {
sc = wait_for_transaction_done(i2c);
}
return sc;
}
static bool can_tx_for_rx(volatile lpc32xx_i2c *i2c)
{
return (i2c->stat & (I2C_STAT_TFF | I2C_STAT_RFF)) == 0;
}
static bool can_rx(volatile lpc32xx_i2c *i2c)
{
return (i2c->stat & I2C_STAT_RFE) == 0;
}
rtems_status_code lpc32xx_i2c_read_with_optional_stop(
volatile lpc32xx_i2c *i2c,
uint8_t *in,
size_t n,
bool stop
)
{
rtems_status_code sc = RTEMS_SUCCESSFUL;
size_t last = n - 1;
size_t rx = 0;
size_t tx = 0;
if (!stop) {
return RTEMS_NOT_IMPLEMENTED;
}
while (rx <= last) {
while (tx < last && can_tx_for_rx(i2c)) {
i2c->rx_or_tx = 0;
++tx;
}
if (tx == last && can_tx_for_rx(i2c)) {
uint32_t stop_flag = stop ? I2C_TX_STOP : 0;
i2c->rx_or_tx = stop_flag;
++tx;
}
while (rx <= last && can_rx(i2c)) {
in [rx] = (uint8_t) i2c->rx_or_tx;
++rx;
}
}
if (stop) {
sc = wait_for_transaction_done(i2c);
}
return sc;
}
rtems_status_code lpc32xx_i2c_write_and_read(
volatile lpc32xx_i2c *i2c,
unsigned addr,
const uint8_t *out,
size_t out_size,
uint8_t *in,
size_t in_size
)
{
rtems_status_code sc = RTEMS_SUCCESSFUL;
if (out_size > 0) {
bool stop = in_size == 0;
sc = lpc32xx_i2c_write_start(i2c, addr);
if (sc != RTEMS_SUCCESSFUL) {
return sc;
}
sc = lpc32xx_i2c_write_with_optional_stop(i2c, out, out_size, stop);
if (sc != RTEMS_SUCCESSFUL) {
return sc;
}
}
if (in_size > 0) {
sc = lpc32xx_i2c_read_start(i2c, addr);
if (sc != RTEMS_SUCCESSFUL) {
return sc;
}
lpc32xx_i2c_read_with_optional_stop(i2c, in, in_size, true);
}
return RTEMS_SUCCESSFUL;
}