forked from Imagelibrary/rtems
490 lines
12 KiB
C
490 lines
12 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
|
|
/*
|
|
* Copyright (C) 2020 embedded brains GmbH & Co. KG
|
|
*
|
|
* 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 <bsp.h>
|
|
#include <bsp/fatal.h>
|
|
#include <bsp/fdt.h>
|
|
#include <bsp/irq.h>
|
|
|
|
#include <chip.h>
|
|
#include <dev/i2c/i2c.h>
|
|
#include <fsl_clock.h>
|
|
#include <fsl_lpi2c.h>
|
|
#include <libfdt.h>
|
|
|
|
#define LPI2C_MTDR_CMD_transmit LPI2C_MTDR_CMD(0)
|
|
#define LPI2C_MTDR_CMD_receive LPI2C_MTDR_CMD(1)
|
|
#define LPI2C_MTDR_CMD_stop LPI2C_MTDR_CMD(2)
|
|
#define LPI2C_MTDR_CMD_receive_and_discard LPI2C_MTDR_CMD(3)
|
|
#define LPI2C_MTDR_CMD_start_and_transmit LPI2C_MTDR_CMD(4)
|
|
#define LPI2C_MTDR_CMD_start_and_transmit_NACK LPI2C_MTDR_CMD(5)
|
|
#define LPI2C_MTDR_CMD_start_and_transmit_highspeed LPI2C_MTDR_CMD(6)
|
|
#define LPI2C_MTDR_CMD_start_and_transmit_highspeed_NACK LPI2C_MTDR_CMD(7)
|
|
|
|
#define LPI2C_INT_ERRORS_SERIOUS ( \
|
|
LPI2C_MSR_FEF_MASK | LPI2C_MSR_ALF_MASK | LPI2C_MSR_PLTF_MASK )
|
|
|
|
#define LPI2C_INT_ERROR_NO_ACK (LPI2C_MSR_NDF_MASK)
|
|
|
|
#define LPI2C_INT_ERRORS (LPI2C_INT_ERRORS_SERIOUS | LPI2C_INT_ERROR_NO_ACK)
|
|
|
|
#define LPI2C_INT_ADDRESSED (LPI2C_INT_ERRORS | LPI2C_MSR_TDF_MASK)
|
|
|
|
#define LPI2C_INT_STOP_SENT (LPI2C_INT_ERRORS | LPI2C_MSR_SDF_MASK)
|
|
|
|
#define LPI2C_INT_RECEIVED (LPI2C_INT_ERRORS | LPI2C_MSR_RDF_MASK)
|
|
|
|
#define LPI2C_INT_TRANSMITTED (LPI2C_INT_ERRORS | LPI2C_MSR_TDF_MASK)
|
|
|
|
struct imxrt_lpi2c_bus {
|
|
i2c_bus base;
|
|
volatile LPI2C_Type *regs;
|
|
rtems_vector_number irq;
|
|
uint32_t src_clock_hz;
|
|
clock_ip_name_t clock_ip;
|
|
unsigned long clock;
|
|
|
|
rtems_binary_semaphore sem;
|
|
int eno;
|
|
|
|
uint32_t msg_todo;
|
|
const i2c_msg *msg;
|
|
|
|
/* Everything that is necessary for the current message */
|
|
uint32_t chunk_todo;
|
|
uint16_t buf_todo;
|
|
uint8_t *buf;
|
|
bool stop;
|
|
bool read;
|
|
};
|
|
|
|
static void imxrt_lpi2c_sw_reset(volatile LPI2C_Type *regs)
|
|
{
|
|
regs->MCR = LPI2C_MCR_RST_MASK | LPI2C_MCR_RRF_MASK | LPI2C_MCR_RTF_MASK;
|
|
regs->SCR = LPI2C_SCR_RST_MASK | LPI2C_SCR_RRF_MASK | LPI2C_SCR_RTF_MASK;
|
|
regs->MCR = 0;
|
|
regs->SCR = 0;
|
|
}
|
|
|
|
static int imxrt_lpi2c_set_clock(i2c_bus *base, unsigned long clock)
|
|
{
|
|
struct imxrt_lpi2c_bus *bus;
|
|
volatile LPI2C_Type *regs;
|
|
|
|
bus = (struct imxrt_lpi2c_bus *) base;
|
|
regs = bus->regs;
|
|
|
|
bus->clock = clock;
|
|
|
|
/*
|
|
* Maybe there is a more efficient way than used by that function. But
|
|
* changing clock doesn't happen often. So it should be OK for now.
|
|
*/
|
|
LPI2C_MasterSetBaudRate((LPI2C_Type *)regs, bus->src_clock_hz, clock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void imxrt_lpi2c_do_reinit(
|
|
struct imxrt_lpi2c_bus *bus,
|
|
volatile LPI2C_Type *regs
|
|
)
|
|
{
|
|
regs->MIER = 0;
|
|
imxrt_lpi2c_sw_reset(regs);
|
|
|
|
regs->MCFGR2 = LPI2C_MCFGR2_FILTSDA(0) | LPI2C_MCFGR2_FILTSCL(0) |
|
|
LPI2C_MCFGR2_BUSIDLE(0);
|
|
regs->MCFGR3 = LPI2C_MCFGR3_PINLOW(0);
|
|
|
|
regs->MFCR = LPI2C_MFCR_RXWATER(0) | LPI2C_MFCR_TXWATER(1);
|
|
|
|
imxrt_lpi2c_set_clock(&bus->base, bus->clock);
|
|
}
|
|
|
|
static void imxrt_lpi2c_done(
|
|
struct imxrt_lpi2c_bus *bus,
|
|
volatile LPI2C_Type *regs
|
|
)
|
|
{
|
|
regs->MIER = 0;
|
|
regs->MCR &= ~LPI2C_MCR_MEN_MASK;
|
|
rtems_binary_semaphore_post(&bus->sem);
|
|
}
|
|
|
|
static void imxrt_lpi2c_next_msg(
|
|
struct imxrt_lpi2c_bus *bus,
|
|
volatile LPI2C_Type *regs
|
|
);
|
|
|
|
static void imxrt_lpi2c_transmit_next(
|
|
struct imxrt_lpi2c_bus *bus,
|
|
volatile LPI2C_Type *regs
|
|
)
|
|
{
|
|
if (bus->chunk_todo == 0) {
|
|
/* Check whether a stop has to be send */
|
|
if (bus->stop) {
|
|
regs->MTDR = LPI2C_MTDR_CMD_stop;
|
|
bus->stop = false;
|
|
regs->MIER = LPI2C_INT_STOP_SENT;
|
|
} else {
|
|
imxrt_lpi2c_next_msg(bus, regs);
|
|
}
|
|
} else {
|
|
if (bus->read) {
|
|
uint16_t to_read;
|
|
to_read = MIN(bus->chunk_todo, 256);
|
|
bus->chunk_todo -= to_read;
|
|
|
|
regs->MTDR = LPI2C_MTDR_CMD_receive | (to_read - 1);
|
|
regs->MIER = LPI2C_INT_RECEIVED;
|
|
} else {
|
|
regs->MTDR = LPI2C_MTDR_CMD_transmit | *bus->buf;
|
|
++bus->buf;
|
|
--bus->buf_todo;
|
|
--bus->chunk_todo;
|
|
regs->MIER = LPI2C_INT_TRANSMITTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void imxrt_lpi2c_next_msg(
|
|
struct imxrt_lpi2c_bus *bus,
|
|
volatile LPI2C_Type *regs
|
|
)
|
|
{
|
|
if (bus->msg_todo == 0) {
|
|
imxrt_lpi2c_done(bus, regs);
|
|
} else {
|
|
const i2c_msg *msg;
|
|
int flags;
|
|
bool start;
|
|
uint16_t addr;
|
|
|
|
msg = bus->msg;
|
|
flags = msg->flags;
|
|
|
|
addr = msg->addr;
|
|
start = (flags & I2C_M_NOSTART) == 0;
|
|
bus->read = (flags & I2C_M_RD) != 0;
|
|
bus->chunk_todo = msg->len;
|
|
bus->buf_todo = msg->len;
|
|
bus->buf = msg->buf;
|
|
bus->stop = (flags & I2C_M_STOP) != 0 || bus->msg_todo <= 1;
|
|
|
|
++bus->msg;
|
|
--bus->msg_todo;
|
|
|
|
if (start) {
|
|
uint32_t mtdr;
|
|
mtdr = LPI2C_MTDR_CMD_start_and_transmit;
|
|
mtdr |= addr << 1;
|
|
if (bus->read) {
|
|
mtdr |= 1;
|
|
}
|
|
regs->MTDR = mtdr;
|
|
regs->MIER = LPI2C_INT_ADDRESSED;
|
|
} else {
|
|
imxrt_lpi2c_transmit_next(bus, regs);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void imxrt_lpi2c_interrupt(void *arg)
|
|
{
|
|
struct imxrt_lpi2c_bus *bus;
|
|
volatile LPI2C_Type *regs;
|
|
uint32_t msr;
|
|
|
|
bus = arg;
|
|
regs = bus->regs;
|
|
|
|
msr = regs->MSR;
|
|
regs->MSR = msr;
|
|
|
|
if ((msr & LPI2C_INT_ERROR_NO_ACK) != 0) {
|
|
/* Just end the transmission */
|
|
bus->eno = EIO;
|
|
imxrt_lpi2c_done(bus, regs);
|
|
} else if ((msr & LPI2C_INT_ERRORS_SERIOUS) != 0) {
|
|
/* Some worse error occurred. Reset hardware. */
|
|
bus->eno = EIO;
|
|
imxrt_lpi2c_do_reinit(bus, regs);
|
|
imxrt_lpi2c_done(bus, regs);
|
|
} else {
|
|
uint32_t mrdr;
|
|
while (((mrdr = regs->MRDR) & LPI2C_MRDR_RXEMPTY_MASK) == 0) {
|
|
if (bus->read && bus->buf_todo > 0) {
|
|
*bus->buf = (mrdr & LPI2C_MRDR_DATA_MASK) >> LPI2C_MRDR_DATA_SHIFT;
|
|
++bus->buf;
|
|
--bus->buf_todo;
|
|
}
|
|
}
|
|
|
|
if (
|
|
((msr & LPI2C_MSR_TDF_MASK) != 0) &&
|
|
(!bus->read || bus->chunk_todo > 0 || bus->buf_todo == 0)
|
|
) {
|
|
imxrt_lpi2c_transmit_next(bus, regs);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int imxrt_lpi2c_wait_for_not_busy(volatile LPI2C_Type *regs)
|
|
{
|
|
rtems_interval timeout;
|
|
bool before;
|
|
|
|
if ((regs->MSR & LPI2C_MSR_BBF_MASK) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
timeout = rtems_clock_tick_later_usec(5000);
|
|
|
|
do {
|
|
before = rtems_clock_tick_before(timeout);
|
|
|
|
if ((regs->MSR & LPI2C_MSR_BBF_MASK) == 0) {
|
|
return 0;
|
|
}
|
|
} while (before);
|
|
|
|
return ETIMEDOUT;
|
|
}
|
|
|
|
static void imxrt_lpi2c_first_msg(
|
|
struct imxrt_lpi2c_bus *bus,
|
|
volatile LPI2C_Type *regs
|
|
)
|
|
{
|
|
if ((regs->MCR & LPI2C_MCR_MEN_MASK) == 0) {
|
|
regs->MCR |= LPI2C_MCR_MEN_MASK;
|
|
}
|
|
|
|
imxrt_lpi2c_next_msg(bus, regs);
|
|
}
|
|
|
|
static int imxrt_lpi2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t n)
|
|
{
|
|
struct imxrt_lpi2c_bus *bus;
|
|
volatile LPI2C_Type *regs;
|
|
int supported_flags;
|
|
int eno;
|
|
uint16_t i;
|
|
|
|
bus = (struct imxrt_lpi2c_bus *) base;
|
|
regs = bus->regs;
|
|
|
|
supported_flags = I2C_M_RD | I2C_M_STOP;
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
if ((msgs[i].flags & ~supported_flags) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
supported_flags |= I2C_M_NOSTART;
|
|
}
|
|
|
|
eno = imxrt_lpi2c_wait_for_not_busy(regs);
|
|
if (eno != 0) {
|
|
imxrt_lpi2c_do_reinit(bus, regs);
|
|
return -eno;
|
|
}
|
|
|
|
bus->msg_todo = n;
|
|
bus->msg = &msgs[0];
|
|
bus->eno = 0;
|
|
|
|
imxrt_lpi2c_first_msg(bus, regs);
|
|
|
|
eno = rtems_binary_semaphore_wait_timed_ticks(&bus->sem, bus->base.timeout);
|
|
if (eno != 0) {
|
|
/* Timeout */
|
|
imxrt_lpi2c_do_reinit(bus, regs);
|
|
rtems_binary_semaphore_try_wait(&bus->sem);
|
|
return -eno;
|
|
}
|
|
|
|
return -bus->eno;
|
|
}
|
|
|
|
static void imxrt_lpi2c_destroy(i2c_bus *base)
|
|
{
|
|
struct imxrt_lpi2c_bus *bus;
|
|
volatile LPI2C_Type *regs;
|
|
|
|
bus = (struct imxrt_lpi2c_bus *) base;
|
|
regs = bus->regs;
|
|
imxrt_lpi2c_sw_reset(regs);
|
|
CLOCK_DisableClock(bus->clock_ip);
|
|
|
|
rtems_interrupt_handler_remove(bus->irq, imxrt_lpi2c_interrupt, bus);
|
|
i2c_bus_destroy_and_free(&bus->base);
|
|
}
|
|
|
|
static int imxrt_lpi2c_hw_init(struct imxrt_lpi2c_bus *bus)
|
|
{
|
|
rtems_status_code sc;
|
|
volatile LPI2C_Type *regs;
|
|
|
|
regs = bus->regs;
|
|
|
|
CLOCK_EnableClock(bus->clock_ip);
|
|
|
|
bus->clock = I2C_BUS_CLOCK_DEFAULT;
|
|
imxrt_lpi2c_do_reinit(bus, regs);
|
|
|
|
sc = rtems_interrupt_handler_install(
|
|
bus->irq,
|
|
"LPI2C",
|
|
RTEMS_INTERRUPT_UNIQUE,
|
|
imxrt_lpi2c_interrupt,
|
|
bus
|
|
);
|
|
if (sc != RTEMS_SUCCESSFUL) {
|
|
return EAGAIN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t imxrt_lpi2c_get_src_freq(void)
|
|
{
|
|
uint32_t freq;
|
|
uint32_t mux;
|
|
uint32_t divider;
|
|
|
|
mux = CLOCK_GetMux(kCLOCK_Lpi2cMux);
|
|
divider = 1;
|
|
|
|
switch (mux) {
|
|
case 0: /* pll3_sw_clk */
|
|
freq = CLOCK_GetFreq(kCLOCK_Usb1PllClk);
|
|
divider = 8;
|
|
break;
|
|
case 1: /* OSC */
|
|
freq = CLOCK_GetFreq(kCLOCK_OscClk);
|
|
break;
|
|
default:
|
|
freq = 0;
|
|
}
|
|
|
|
divider *= CLOCK_GetDiv(kCLOCK_Lpi2cDiv) + 1;
|
|
freq /= divider;
|
|
|
|
return freq;
|
|
}
|
|
|
|
static clock_ip_name_t imxrt_lpi2c_clock_ip(volatile LPI2C_Type *regs)
|
|
{
|
|
LPI2C_Type *const base_addresses[] = LPI2C_BASE_PTRS;
|
|
static const clock_ip_name_t lpi2c_clocks[] = LPI2C_CLOCKS;
|
|
size_t i;
|
|
|
|
for (i = 0; i < RTEMS_ARRAY_SIZE(base_addresses); ++i) {
|
|
if (base_addresses[i] == regs) {
|
|
return lpi2c_clocks[i];
|
|
}
|
|
}
|
|
|
|
return kCLOCK_IpInvalid;
|
|
}
|
|
|
|
void imxrt_lpi2c_init(void)
|
|
{
|
|
const void *fdt;
|
|
int node;
|
|
|
|
fdt = bsp_fdt_get();
|
|
node = -1;
|
|
|
|
do {
|
|
node = fdt_node_offset_by_compatible(fdt, node, "nxp,imxrt-lpi2c");
|
|
|
|
if (node >= 0 && imxrt_fdt_node_is_enabled(fdt, node)) {
|
|
struct imxrt_lpi2c_bus *bus;
|
|
int eno;
|
|
const char *bus_path;
|
|
|
|
bus = (struct imxrt_lpi2c_bus*) i2c_bus_alloc_and_init(sizeof(*bus));
|
|
if (bus == NULL) {
|
|
bsp_fatal(IMXRT_FATAL_LPI2C_ALLOC_FAILED);
|
|
}
|
|
|
|
rtems_binary_semaphore_init(&bus->sem, "LPI2C");
|
|
|
|
bus->regs = imx_get_reg_of_node(fdt, node);
|
|
if (bus->regs == NULL) {
|
|
(*bus->base.destroy)(&bus->base);
|
|
bsp_fatal(IMXRT_FATAL_LPI2C_INVALID_FDT);
|
|
}
|
|
|
|
bus->irq = imx_get_irq_of_node(fdt, node, 0);
|
|
if (bus->irq == BSP_INTERRUPT_VECTOR_INVALID) {
|
|
(*bus->base.destroy)(&bus->base);
|
|
bsp_fatal(IMXRT_FATAL_LPI2C_INVALID_FDT);
|
|
}
|
|
|
|
bus_path = fdt_getprop(fdt, node, "rtems,path", NULL);
|
|
if (bus_path == NULL) {
|
|
(*bus->base.destroy)(&bus->base);
|
|
bsp_fatal(IMXRT_FATAL_LPI2C_INVALID_FDT);
|
|
}
|
|
|
|
bus->clock_ip = imxrt_lpi2c_clock_ip(bus->regs);
|
|
bus->src_clock_hz = imxrt_lpi2c_get_src_freq();
|
|
|
|
eno = imxrt_lpi2c_hw_init(bus);
|
|
if (eno != 0) {
|
|
(*bus->base.destroy)(&bus->base);
|
|
bsp_fatal(IMXRT_FATAL_LPI2C_HW_INIT_FAILED);
|
|
}
|
|
|
|
bus->base.transfer = imxrt_lpi2c_transfer;
|
|
bus->base.set_clock = imxrt_lpi2c_set_clock;
|
|
bus->base.destroy = imxrt_lpi2c_destroy;
|
|
|
|
/*
|
|
* Need at least three FIFO bytes:
|
|
* 1. One to two data to transmit or receive.
|
|
* Two is necessary for long receives without NACK.
|
|
* 2. A stop condition.
|
|
*/
|
|
if ((1 << ((bus->regs->PARAM & LPI2C_PARAM_MTXFIFO_MASK) >>
|
|
LPI2C_PARAM_MTXFIFO_SHIFT)) < 3) {
|
|
bsp_fatal(IMXRT_FATAL_LPI2C_UNSUPPORTED_HARDWARE);
|
|
}
|
|
|
|
eno = i2c_bus_register(&bus->base, bus_path);
|
|
if (eno != 0) {
|
|
bsp_fatal(IMXRT_FATAL_LPI2C_REGISTER_FAILED);
|
|
}
|
|
}
|
|
} while (node >= 0);
|
|
}
|