summaryrefslogblamecommitdiffstats
path: root/bsps/arm/imxrt/i2c/imxrt-lpi2c.c
blob: 9b983f24af721a368665e4e6d1194f840885aa02 (plain) (tree)
1
2
3
4


                                           
                                                   


















































































































































































































































































































































































                                                                              
                                                                  

                
                       


                   

                                                     
















                                               










                                                                             




























































                                                                           
                                                                  




























                                                                 
/* 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(clock_ip_name_t clock_ip)
{
  uint32_t freq;
#if IMXRT_IS_MIMXRT10xx
  uint32_t mux;
  uint32_t divider;

  (void) clock_ip; /* Not necessary for i.MXRT1050 */

  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;
#elif IMXRT_IS_MIMXRT11xx
  /*
   * FIXME: A future version of the mcux_sdk might provide a better method to
   * get the clock instead of this hack.
   */
  clock_root_t clock_root = clock_ip + kCLOCK_Root_Lpi2c1 - kCLOCK_Lpi2c1;

  freq = CLOCK_GetRootClockFreq(clock_root);
#else
  #error Getting I2C frequency is not implemented for this chip.
#endif

  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(bus->clock_ip);

      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);
}