diff --git a/Documentation/platforms/arm/am67/boards/t3-gem-o1/index.rst b/Documentation/platforms/arm/am67/boards/t3-gem-o1/index.rst index 5b1eee618a04d..aad627e51b381 100644 --- a/Documentation/platforms/arm/am67/boards/t3-gem-o1/index.rst +++ b/Documentation/platforms/arm/am67/boards/t3-gem-o1/index.rst @@ -100,6 +100,7 @@ main-domain R5F core: - **GPIO:** pad configuration and read/write over the AM67 GPIO controller. - **SPI:** MCU_MCSPI0 master, with the hardware chip-select driven per channel (used for the on-board ICM-20948 IMU and LPS22DF barometer). +- **I2C:** WKUP_I2C0 master, registered as /dev/i2c2. Installation ============ diff --git a/arch/arm/include/am67/irq.h b/arch/arm/include/am67/irq.h index 2b32acd7e06e7..3150b90672e50 100644 --- a/arch/arm/include/am67/irq.h +++ b/arch/arm/include/am67/irq.h @@ -292,7 +292,8 @@ /**************************************************************************** * Public Data ****************************************************************************/ - +#define AM67_IRQ_I2C0 CSLR_R5FSS0_CORE0_INTR_MCU_I2C0_POINTRPEND_0 +#define AM67_IRQ_WKUP_I2C0 CSLR_R5FSS0_CORE0_INTR_WKUP_I2C0_POINTRPEND_0 /**************************************************************************** * Inline Functions ****************************************************************************/ diff --git a/arch/arm/src/am67/Kconfig b/arch/arm/src/am67/Kconfig index 5ae7c094a1ce1..352105c0bf037 100644 --- a/arch/arm/src/am67/Kconfig +++ b/arch/arm/src/am67/Kconfig @@ -28,3 +28,24 @@ config AM67_MCSPI0_FCLK default 48000000 endif + +config AM67_I2C + bool "AM67 I2C support" + default n + depends on I2C + +if AM67_I2C + +config AM67_I2C0 + bool "AM67 MCU_I2C0 driver (MCU domain, 0x04900000)" + default n + ---help--- + Enable the I2C master driver for MCU_I2C0. + +config AM67_WKUP_I2C0 + bool "AM67 WKUP_I2C0 driver (WKUP domain, 0x2b200000)" + default n + ---help--- + Enable the I2C master driver for WKUP_I2C0. + +endif # AM67_I2C diff --git a/arch/arm/src/am67/Make.defs b/arch/arm/src/am67/Make.defs index f464f63ae7312..ba6ca865ea04d 100644 --- a/arch/arm/src/am67/Make.defs +++ b/arch/arm/src/am67/Make.defs @@ -36,4 +36,7 @@ endif ifeq ($(CONFIG_AM67_MCSPI0),y) CHIP_CSRCS += am67_mcspi.c endif +ifeq ($(CONFIG_AM67_I2C),y) +CHIP_CSRCS += am67_i2c.c +endif CHIP_CSRCS += arm_mpu.c diff --git a/arch/arm/src/am67/am67_i2c.c b/arch/arm/src/am67/am67_i2c.c new file mode 100644 index 0000000000000..bd4d74cb15b14 --- /dev/null +++ b/arch/arm/src/am67/am67_i2c.c @@ -0,0 +1,1478 @@ +/**************************************************************************** + * arch/arm/src/am67/am67_i2c.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "arm_internal.h" +#include "am67_pinmux.h" +#include "am67_i2c.h" +#include "am67_i2c_hw.h" + +/* At least one I2C peripheral must be enabled */ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define AM67_I2C_SCLK (96000000) /* 96 MHz */ + +/* Configuration ************************************************************/ + +/* CONFIG_I2C_POLLED may be set so that I2C interrupts will not be used. + * Instead, CPU-intensive polling will be used. + */ + +/* Interrupt wait timeout in seconds and milliseconds */ + +#if !defined(CONFIG_AM67_I2CTIMEOSEC) && !defined(CONFIG_AM67_I2CTIMEOMS) +# define CONFIG_AM67_I2CTIMEOSEC 0 +# define CONFIG_AM67_I2CTIMEOMS 500 /* Default is 500 milliseconds */ +#elif !defined(CONFIG_AM67_I2CTIMEOSEC) +# define CONFIG_AM67_I2CTIMEOSEC 0 /* User provided milliseconds */ +#elif !defined(CONFIG_AM67_I2CTIMEOMS) +# define CONFIG_AM67_I2CTIMEOMS 0 /* User provided seconds */ +#endif + +/* Interrupt wait time timeout in system timer ticks */ + +#ifndef CONFIG_AM67_I2CTIMEOTICKS +# define CONFIG_AM67_I2CTIMEOTICKS \ + (SEC2TICK(CONFIG_AM67_I2CTIMEOSEC) + MSEC2TICK(CONFIG_AM67_I2CTIMEOMS)) +#endif + +#ifndef CONFIG_AM67_I2C_DYNTIMEO_STARTSTOP +# define CONFIG_AM67_I2C_DYNTIMEO_STARTSTOP TICK2USEC(CONFIG_AM67_I2CTIMEOTICKS) +#endif + +/* Debug ********************************************************************/ + +/* I2C event trace logic. NOTE: trace uses the internal, non-standard, + * low-level debug interface syslog() but does not require that any other + * debug is enabled. + */ + +#ifndef CONFIG_I2C_TRACE +# define am67_i2c_tracereset(p) +# define am67_i2c_tracenew(p,s) +# define am67_i2c_traceevent(p,e,a) +# define am67_i2c_tracedump(p) +#endif + +#ifndef CONFIG_I2C_NTRACE +# define CONFIG_I2C_NTRACE 32 +#endif + +#ifdef CONFIG_I2C_SLAVE +# error I2C slave logic is not supported yet for AM67 +#endif + +#define I2C_MASTER 1 +#define I2C_SLAVE 2 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Interrupt state */ + +enum am67_intstate_e +{ + INTSTATE_IDLE = 0, /* No I2C activity */ + INTSTATE_WAITING, /* Waiting for completion of interrupt activity */ + INTSTATE_DONE, /* Interrupt activity complete */ +}; + +/* Trace events */ + +enum am67_trace_e +{ + I2CEVENT_NONE = 0, /* No events have occurred with this status */ + I2CEVENT_SENDADDR, /* Start/Master bit set and address sent, param = msgc */ + I2CEVENT_SENDBYTE, /* Send byte, param = dcnt */ + I2CEVENT_RCVBYTE, /* Read more dta, param = dcnt */ + I2CEVENT_NOSTART, /* BTF on last byte with no restart, param = msgc */ + I2CEVENT_STARTRESTART, /* Last byte sent, re-starting, param = msgc */ + I2CEVENT_STOP, /* Last byte sten, send stop, param = 0 */ + I2CEVENT_ERROR /* Error occurred, param = 0 */ +}; + +/* Trace data */ + +struct am67_trace_s +{ + uint32_t status; /* IRQSTATUS at the time of the event */ + uint32_t count; /* Interrupt count when status change */ + enum am67_trace_e event; /* Last event that occurred with this status */ + uint32_t parm; /* Parameter associated with the event */ + clock_t time; /* First of event or first status */ +}; + +/* I2C Device hardware configuration */ + +struct am67_i2c_config_s +{ + uint32_t base; /* I2C base address */ + uint8_t mode; /* Master or Slave mode */ +#ifndef CONFIG_I2C_POLLED + uint32_t irq; /* Event IRQ */ +#endif +}; + +/* I2C Device Private Data */ + +struct am67_i2c_priv_s +{ + /* Standard I2C operations */ + + const struct i2c_ops_s *ops; + + /* Port configuration */ + + const struct am67_i2c_config_s *config; + + int refs; /* Reference count */ + bool inited; /* HW brought up lazily on the first transfer */ + mutex_t lock; /* Mutual exclusion mutex */ +#ifndef CONFIG_I2C_POLLED + sem_t sem_isr; /* Interrupt wait semaphore */ +#endif + volatile uint8_t intstate; /* Interrupt handshake (see enum am67_intstate_e) */ + + uint8_t msgc; /* Message count */ + struct i2c_msg_s *msgv; /* Message list */ + uint8_t *ptr; /* Current message buffer */ + uint32_t frequency; /* Current I2C frequency */ + int dcnt; /* Current message length */ + uint16_t flags; /* Current message flags */ + + /* I2C trace support */ + +#ifdef CONFIG_I2C_TRACE + int tndx; /* Trace array index */ + clock_t start_time; /* Time when the trace was started */ + + /* The actual trace data */ + + struct am67_trace_s trace[CONFIG_I2C_NTRACE]; +#endif + + uint32_t status; /* End of transfer IRQSTATUS */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static inline uint32_t am67_i2c_getreg(struct am67_i2c_priv_s *priv, + uint16_t offset); +static inline void am67_i2c_putreg(struct am67_i2c_priv_s *priv, + uint16_t offset, uint32_t value); +static inline void am67_i2c_modifyreg(struct am67_i2c_priv_s *priv, + uint16_t offset, uint32_t clearbits, + uint32_t setbits); + +#ifdef CONFIG_AM67_I2C_DYNTIMEO +static uint32_t am67_i2c_toticks(int msgc, struct i2c_msg_s *msgs); +#endif /* CONFIG_AM67_I2C_DYNTIMEO */ + +static inline int +am67_i2c_sem_waitdone(struct am67_i2c_priv_s *priv); +static inline bool +am67_i2c_sem_waitstop(struct am67_i2c_priv_s *priv); + +#ifdef CONFIG_I2C_TRACE +static void am67_i2c_tracereset(struct am67_i2c_priv_s *priv); +static void am67_i2c_tracenew(struct am67_i2c_priv_s *priv, + uint32_t status); +static void am67_i2c_traceevent(struct am67_i2c_priv_s *priv, + enum am67_trace_e event, uint32_t parm); +static void am67_i2c_tracedump(struct am67_i2c_priv_s *priv); +#endif /* CONFIG_I2C_TRACE */ + +static void am67_i2c_setclock(struct am67_i2c_priv_s *priv, + uint32_t frequency); +static void am67_i2c_startmsg(struct am67_i2c_priv_s *priv); +static inline void am67_i2c_sendstop(struct am67_i2c_priv_s *priv); +static inline uint32_t +am67_i2c_getstatus(struct am67_i2c_priv_s *priv); + +static int am67_i2c_isr_process(struct am67_i2c_priv_s *priv); + +#ifndef CONFIG_I2C_POLLED +static int am67_i2c_isr(int irq, void *context, void *arg); +#endif /* !CONFIG_I2C_POLLED */ + +static int am67_i2c_init(struct am67_i2c_priv_s *priv); +static int am67_i2c_deinit(struct am67_i2c_priv_s *priv); +static int am67_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Trace events strings */ + +#ifdef CONFIG_I2C_TRACE +static const char *g_trace_names[] = +{ + "NONE ", + "SENDADDR ", + "SENDBYTE ", + "RCVBYTE ", + "NOSTART ", + "START/RESTART ", + "STOP ", + "ERROR " +}; +#endif + +/* I2C interface */ + +static const struct i2c_ops_s am67_i2c_ops = +{ + .transfer = am67_i2c_transfer +}; + +/* I2C device structures */ + +#ifdef CONFIG_AM67_I2C0 +static const struct am67_i2c_config_s am67_i2c0_config = +{ + .base = AM67_I2C0_VADDR, +#ifndef CONFIG_I2C_SLAVE + .mode = I2C_MASTER, +#else + .mode = I2C_SLAVE, +#endif +#ifndef CONFIG_I2C_POLLED + .irq = AM67_IRQ_I2C0, +#endif +}; + +static struct am67_i2c_priv_s am67_i2c0_priv = +{ + .ops = &am67_i2c_ops, + .config = &am67_i2c0_config, + .refs = 0, + .lock = NXMUTEX_INITIALIZER, +#ifndef CONFIG_I2C_POLLED + .sem_isr = SEM_INITIALIZER(0), +#endif + .intstate = INTSTATE_IDLE, + .msgc = 0, + .msgv = NULL, + .ptr = NULL, + .dcnt = 0, + .flags = 0, + .status = 0 +}; +#endif + +#ifdef CONFIG_AM67_WKUP_I2C0 +static const struct am67_i2c_config_s am67_i2c2_config = +{ + .base = AM67_WKUP_I2C0_VADDR, +#ifndef CONFIG_I2C_SLAVE + .mode = I2C_MASTER, +#else + .mode = I2C_SLAVE, +#endif +#ifndef CONFIG_I2C_POLLED + .irq = AM67_IRQ_WKUP_I2C0, +#endif +}; + +static struct am67_i2c_priv_s am67_i2c2_priv = +{ + .ops = &am67_i2c_ops, + .config = &am67_i2c2_config, + .refs = 0, + .lock = NXMUTEX_INITIALIZER, +#ifndef CONFIG_I2C_POLLED + .sem_isr = SEM_INITIALIZER(0), +#endif + .intstate = INTSTATE_IDLE, + .msgc = 0, + .msgv = NULL, + .ptr = NULL, + .dcnt = 0, + .flags = 0, + .status = 0 +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: am67_i2c_getreg + * + * Description: + * Get a 32-bit register value by offset + * + ****************************************************************************/ + +static inline uint32_t am67_i2c_getreg(struct am67_i2c_priv_s *priv, + uint16_t offset) +{ + return getreg32(priv->config->base + offset); +} + +/**************************************************************************** + * Name: am67_i2c_putreg + * + * Description: + * Put a 32-bit register value by offset + * + ****************************************************************************/ + +static inline void am67_i2c_putreg(struct am67_i2c_priv_s *priv, + uint16_t offset, uint32_t value) +{ + putreg32(value, priv->config->base + offset); +} + +/**************************************************************************** + * Name: am67_i2c_modifyreg + * + * Description: + * Modify a 32-bit register value by offset + * + ****************************************************************************/ + +static inline void am67_i2c_modifyreg(struct am67_i2c_priv_s *priv, + uint16_t offset, uint32_t clearbits, + uint32_t setbits) +{ + modifyreg32(priv->config->base + offset, clearbits, setbits); +} + +/**************************************************************************** + * Name: am67_i2c_toticks + * + * Description: + * Return a micro-second delay based on the number of bytes left to be + * processed. + * + ****************************************************************************/ + +#ifdef CONFIG_AM67_I2C_DYNTIMEO +static uint32_t am67_i2c_toticks(int msgc, struct i2c_msg_s *msgs) +{ + size_t bytecount = 0; + int i; + + /* Count the number of bytes left to process */ + + for (i = 0; i < msgc; i++) + { + bytecount += msgs[i].length; + } + + /* Then return a number of microseconds based on a user provided scaling + * factor. + */ + + return USEC2TICK(CONFIG_AM67_I2C_DYNTIMEO_USECPERBYTE * bytecount); +} +#endif + +/**************************************************************************** + * Name: am67_i2c_sem_waitdone + * + * Description: + * Wait for a transfer to complete + * + ****************************************************************************/ + +#ifndef CONFIG_I2C_POLLED +static inline int am67_i2c_sem_waitdone(struct am67_i2c_priv_s *priv) +{ + irqstate_t flags; + int ret; + + flags = enter_critical_section(); + + /* Signal the interrupt handler that we are waiting, then start the + * first segment. startmsg() arms exactly the interrupts the segment + * needs; the ISR sequences all remaining segments on ARDY. NOTE: + * Interrupts are currently disabled but will be temporarily re-enabled + * below when nxsem_tickwait() sleeps. + */ + + priv->intstate = INTSTATE_WAITING; + am67_i2c_startmsg(priv); + + do + { + /* Wait until either the transfer is complete or the timeout expires */ + +#ifdef CONFIG_AM67_I2C_DYNTIMEO + ret = nxsem_tickwait(&priv->sem_isr, + am67_i2c_toticks(priv->msgc, priv->msgv)); +#else + ret = nxsem_tickwait(&priv->sem_isr, + CONFIG_AM67_I2CTIMEOTICKS); +#endif + if (ret < 0 && ret != -EINTR) + { + /* Break out of the loop on irrecoverable errors. This would + * include timeouts and mystery errors reported by nxsem_tickwait. + * NOTE that we try again if we are awakened by a signal (EINTR). + */ + + break; + } + } + + /* Loop until the interrupt level transfer is complete. */ + + while (priv->intstate != INTSTATE_DONE); + + /* Set the interrupt state back to IDLE */ + + priv->intstate = INTSTATE_IDLE; + + /* Disable I2C interrupts */ + + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_CLR_OFFSET, I2C_ICR_CLEARMASK); + + leave_critical_section(flags); + return ret; +} +#else +static inline int am67_i2c_sem_waitdone(struct am67_i2c_priv_s *priv) +{ + clock_t timeout; + clock_t start; + clock_t elapsed; + int ret; + + /* Get the timeout value */ + +#ifdef CONFIG_AM67_I2C_DYNTIMEO + timeout = am67_i2c_toticks(priv->msgc, priv->msgv); +#else + timeout = CONFIG_AM67_I2CTIMEOTICKS; +#endif + + priv->intstate = INTSTATE_WAITING; + + /* Start the first segment; the polling loop below sequences the rest */ + + am67_i2c_startmsg(priv); + + start = clock_systime_ticks(); + + do + { + /* Calculate the elapsed time */ + + elapsed = clock_systime_ticks() - start; + + /* Poll by simply calling the timer interrupt handler until it + * reports that it is done. + */ + + am67_i2c_isr_process(priv); + } + + /* Loop until the transfer is complete. */ + + while (priv->intstate != INTSTATE_DONE && elapsed < timeout); + + i2cinfo("intstate: %d elapsed: %ld threshold: %ld status: %08" PRIx32 + "\n", priv->intstate, (long)elapsed, (long)timeout, priv->status); + + /* Set the interrupt state back to IDLE */ + + ret = priv->intstate == INTSTATE_DONE ? OK : -ETIMEDOUT; + priv->intstate = INTSTATE_IDLE; + return ret; +} +#endif + +/**************************************************************************** + * Name: am67_i2c_sem_waitstop + * + * Description: + * Wait for a STOP to complete + * + ****************************************************************************/ + +static inline bool +am67_i2c_sem_waitstop(struct am67_i2c_priv_s *priv) +{ + clock_t start; + clock_t elapsed; + clock_t timeout; + uint32_t regval; + + /* Select a timeout */ + +#ifdef CONFIG_AM67_I2C_DYNTIMEO + timeout = USEC2TICK(CONFIG_AM67_I2C_DYNTIMEO_STARTSTOP); +#else + timeout = CONFIG_AM67_I2CTIMEOTICKS; +#endif + + /* Wait as stop might still be in progress; but stop might also + * be set because of a timeout error: "The [STOP] bit is set and + * cleared by software, cleared by hardware when a Stop condition is + * detected, set by hardware when a timeout error is detected." + */ + + start = clock_systime_ticks(); + do + { + /* Calculate the elapsed time */ + + elapsed = clock_systime_ticks() - start; + + /* Check for Bus Free condition */ + + regval = am67_i2c_getreg(priv, AM67_I2C_IRQ_STAT_RAW_OFFSET); + if ((regval & I2C_IRQ_BB) == 0) + { + return true; + } + } + + /* Loop until the stop is complete or a timeout occurs. */ + + while (elapsed < timeout); + + /* If we get here then a timeout occurred with the STOP condition + * still pending. + */ + + i2cinfo("Timeout with Status Register: %" PRIx32 "\n", regval); + return false; +} + +/**************************************************************************** + * Name: am67_i2c_trace* + * + * Description: + * I2C trace instrumentation + * + ****************************************************************************/ + +#ifdef CONFIG_I2C_TRACE +static void am67_i2c_traceclear(struct am67_i2c_priv_s *priv) +{ + struct am67_trace_s *trace = &priv->trace[priv->tndx]; + + trace->status = 0; /* I2C 32-bit SR2|SR1 status */ + trace->count = 0; /* Interrupt count when status change */ + trace->event = I2CEVENT_NONE; /* Last event that occurred with this status */ + trace->parm = 0; /* Parameter associated with the event */ + trace->time = 0; /* Time of first status or event */ +} + +static void am67_i2c_tracereset(struct am67_i2c_priv_s *priv) +{ + /* Reset the trace info for a new data collection */ + + priv->tndx = 0; + priv->start_time = clock_systime_ticks(); + am67_i2c_traceclear(priv); +} + +static void am67_i2c_tracenew(struct am67_i2c_priv_s *priv, + uint32_t status) +{ + struct am67_trace_s *trace = &priv->trace[priv->tndx]; + + /* Is the current entry uninitialized? Has the status changed? */ + + if (trace->count == 0 || status != trace->status) + { + /* Yes.. Was it the status changed? */ + + if (trace->count != 0) + { + /* Yes.. bump up the trace index (unless out of trace entries) */ + + if (priv->tndx >= (CONFIG_I2C_NTRACE - 1)) + { + i2cerr("ERROR: Trace table overflow\n"); + return; + } + + priv->tndx++; + trace = &priv->trace[priv->tndx]; + } + + /* Initialize the new trace entry */ + + am67_i2c_traceclear(priv); + trace->status = status; + trace->count = 1; + trace->time = clock_systime_ticks(); + } + else + { + /* Just increment the count of times that we have seen this status */ + + trace->count++; + } +} + +static void am67_i2c_traceevent(struct am67_i2c_priv_s *priv, + enum am67_trace_e event, uint32_t parm) +{ + struct am67_trace_s *trace; + + if (event != I2CEVENT_NONE) + { + trace = &priv->trace[priv->tndx]; + + /* Initialize the new trace entry */ + + trace->event = event; + trace->parm = parm; + + /* Bump up the trace index (unless we are out of trace entries) */ + + if (priv->tndx >= (CONFIG_I2C_NTRACE - 1)) + { + i2cerr("ERROR: Trace table overflow\n"); + return; + } + + priv->tndx++; + am67_i2c_traceclear(priv); + } +} + +static void am67_i2c_tracedump(struct am67_i2c_priv_s *priv) +{ + struct am67_trace_s *trace; + int i; + + syslog(LOG_DEBUG, "Elapsed time: %ld\n", + (long)(clock_systime_ticks() - priv->start_time)); + + for (i = 0; i < priv->tndx; i++) + { + trace = &priv->trace[i]; + syslog(LOG_DEBUG, + "%2d. STATUS: %08x COUNT: %3d EVENT: %s(%2d) PARM: %08x " + "TIME: %d\n", + i + 1, trace->status, trace->count, + g_trace_names[trace->event], + trace->event, trace->parm, trace->time - priv->start_time); + } +} +#endif /* CONFIG_I2C_TRACE */ + +/**************************************************************************** + * Name: am67_i2c_setclock + * + * Description: + * Set the I2C clock + * + ****************************************************************************/ + +static void am67_i2c_setclock(struct am67_i2c_priv_s *priv, + uint32_t frequency) +{ + uint32_t src_freq = AM67_I2C_SCLK; + uint32_t men; + uint32_t prescale = 0; + uint32_t scl = 0; + uint32_t scl_low = 0; + uint32_t scl_hi = 0; + uint32_t best_prescale = 0; + uint32_t best_scl_low = 0; + uint32_t best_scl_hi = 0; + uint32_t abs_error = 0; + uint32_t best_error = 0xffffffff; + uint32_t computed_rate; + + /* Has the I2C bus frequency changed? */ + + if (priv->config->mode == I2C_MASTER) + { + if (frequency != priv->frequency) + { + /* Disable the selected I2C peripheral to configure the new + * clock if it is enabled. + */ + + men = am67_i2c_getreg(priv, AM67_I2C_CON_OFFSET) & I2C_CON_EN; + if (men) + { + am67_i2c_modifyreg(priv, AM67_I2C_CON_OFFSET, + I2C_CON_EN, 0); + } + + /* I2C bus clock is: + * Source Clock (Hz) / ((psc + 1) * (scll + 7 + sclh + 5)) + */ + + for (scl = 14; scl < 522; scl += 2) + { + for (prescale = 3; prescale < 256; prescale++) + { + scl_low = (scl / 2) - 7; + scl_hi = (scl / 2) - 5; + + computed_rate = src_freq / (prescale + 1); + computed_rate /= scl_low + 7 + scl_hi + 5; + + if (frequency > computed_rate) + { + abs_error = frequency - computed_rate; + } + else + { + abs_error = computed_rate - frequency; + } + + if (abs_error < best_error) + { + best_prescale = prescale; + best_scl_low = scl_low; + best_scl_hi = scl_hi; + best_error = abs_error; + + if (abs_error == 0) + { + scl = 522; + break; + } + } + } + } + + am67_i2c_putreg(priv, AM67_I2C_PSC_OFFSET, best_prescale); + + am67_i2c_putreg(priv, AM67_I2C_SCLL_OFFSET, best_scl_low); + + am67_i2c_putreg(priv, AM67_I2C_SCLH_OFFSET, best_scl_hi); + + /* Re-enable I2C if it was enabled previously */ + + if (men) + { + am67_i2c_modifyreg(priv, AM67_I2C_CON_OFFSET, + 0, I2C_CON_EN); + } + + /* Save the new I2C frequency */ + + priv->frequency = frequency; + } + } +} + +/**************************************************************************** + * Name: am67_i2c_startmsg + * + * Description: + * Program the hardware for the next message segment and issue a + * (repeated) START. Must only be called while the module is register- + * accessible: before any transfer has started, or from the ISR when + * ARDY is set. + * + ****************************************************************************/ + +static void am67_i2c_startmsg(struct am67_i2c_priv_s *priv) +{ + struct i2c_msg_s *msg = priv->msgv; + uint32_t regval; + + /* Bookkeeping: this segment's buffer/count/flags, advance the list */ + + priv->ptr = msg->buffer; + priv->dcnt = msg->length; + priv->flags = msg->flags; + priv->msgv++; + priv->msgc--; + + /* SA and DCOUNT before anything starts. DCOUNT is written exactly + * once per segment and never touched again while it runs. + */ + + am67_i2c_putreg(priv, AM67_I2C_SA_OFFSET, msg->addr); + am67_i2c_putreg(priv, AM67_I2C_CNT_OFFSET, msg->length); + + /* Direction + master mode. STP is deliberately NOT pre-armed here: + * when priming from the ARDY window the master is holding an open + * transaction, and writing STP=1 there generates an immediate STOP + * (this is why the TI SDK never sets STOP in its restart path). Every + * segment therefore ends with the bus held, and the ISR issues the + * final STOP explicitly once the last segment's data is done. + */ + + regval = I2C_CON_EN | I2C_CON_MST; + if ((priv->flags & I2C_M_READ) == 0) + { + regval |= I2C_CON_TRX; + } + + if ((priv->flags & I2C_M_TEN) != 0) + { + regval |= I2C_CON_XSA; + } + + am67_i2c_putreg(priv, AM67_I2C_CON_OFFSET, regval); + +#ifndef CONFIG_I2C_POLLED + /* Arm this segment's interrupts: errors, ARDY (segment done), and the + * data direction actually in use. BF stays off until we need it. + */ + + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_CLR_OFFSET, + I2C_IRQ_XRDY | I2C_IRQ_RRDY | I2C_IRQ_BF); + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_SET_OFFSET, + I2C_IRQ_AL | I2C_IRQ_NACK | I2C_IRQ_AERR | I2C_IRQ_ARDY | + (((priv->flags & I2C_M_READ) != 0) ? + I2C_IRQ_RRDY : I2C_IRQ_XRDY)); +#endif + + /* Everything is described — now press go */ + + if ((priv->flags & I2C_M_NOSTART) == 0) + { + am67_i2c_traceevent(priv, I2CEVENT_SENDADDR, priv->msgc); + am67_i2c_modifyreg(priv, AM67_I2C_CON_OFFSET, 0, I2C_CON_STT); + } + else + { + am67_i2c_traceevent(priv, I2CEVENT_NOSTART, priv->msgc); + } +} + +/**************************************************************************** + * Name: am67_i2c_sendstop + * + * Description: + * Send the STOP conditions + * + ****************************************************************************/ + +static inline void am67_i2c_sendstop(struct am67_i2c_priv_s *priv) +{ + am67_i2c_modifyreg(priv, AM67_I2C_CON_OFFSET, 0, I2C_CON_STP); +} + +/**************************************************************************** + * Name: am67_i2c_getstatus + * + * Description: + * Get 32-bit status + * + ****************************************************************************/ + +static inline uint32_t +am67_i2c_getstatus(struct am67_i2c_priv_s *priv) +{ +#ifndef CONFIG_I2C_POLLED + return am67_i2c_getreg(priv, AM67_I2C_IRQ_STAT_OFFSET); +#else + return am67_i2c_getreg(priv, AM67_I2C_IRQ_STAT_RAW_OFFSET); +#endif +} + +/**************************************************************************** + * Name: am67_i2c_isr_process + * + * Description: + * Common Interrupt Service Routine + * + ****************************************************************************/ + +static int am67_i2c_isr_process(struct am67_i2c_priv_s *priv) +{ + uint32_t status = am67_i2c_getstatus(priv); + uint32_t raw = am67_i2c_getreg(priv, AM67_I2C_IRQ_STAT_RAW_OFFSET); + uint32_t fatal = raw & (I2C_IRQ_NACK | I2C_IRQ_AL | I2C_IRQ_AERR); + + /* Check for new trace setup */ + + am67_i2c_tracenew(priv, status); + + /* Rung 1: fatal errors preempt everything. Checked in the RAW status + * so an error can never hide behind interrupt masking. + */ + + if (fatal != 0) + { + am67_i2c_traceevent(priv, I2CEVENT_ERROR, fatal); + + /* Release the bus and reset the transaction engine, mirroring the + * TI SDK fatal-error path. Without this a single NACK leaves the + * master mid-transaction and every later transfer times out. + */ + + am67_i2c_sendstop(priv); + + am67_i2c_putreg(priv, AM67_I2C_IRQ_STAT_OFFSET, I2C_STS_CLEARMASK); + am67_i2c_putreg(priv, AM67_I2C_BUF_OFFSET, + I2C_BUF_TXFIFO_CLR | I2C_BUF_RXFIFO_CLR); + am67_i2c_putreg(priv, AM67_I2C_CNT_OFFSET, 0); + + priv->status = status | fatal; + priv->msgv = NULL; + priv->dcnt = 0; + priv->ptr = NULL; + +#ifndef CONFIG_I2C_POLLED + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_CLR_OFFSET, I2C_ICR_CLEARMASK); + + if (priv->intstate == INTSTATE_WAITING) + { + nxsem_post(&priv->sem_isr); + priv->intstate = INTSTATE_DONE; + } +#else + priv->intstate = INTSTATE_DONE; +#endif + return OK; + } + + /* Rung 2: a byte arrived — drain it BEFORE any phase decision so a + * pending restart/stop cannot strand it in the FIFO. + */ + + if ((status & I2C_IRQ_RRDY) != 0) + { + if (priv->dcnt > 0 && priv->ptr != NULL) + { + am67_i2c_traceevent(priv, I2CEVENT_RCVBYTE, priv->dcnt); + *priv->ptr++ = am67_i2c_getreg(priv, AM67_I2C_DATA_OFFSET) & + I2C_DATA_MASK; + priv->dcnt--; + } + else + { + /* Unexpected byte: consume it so the FIFO cannot wedge */ + + am67_i2c_getreg(priv, AM67_I2C_DATA_OFFSET); + } + + am67_i2c_putreg(priv, AM67_I2C_IRQ_STAT_OFFSET, I2C_IRQ_RRDY); + } + + /* Rung 3: the transmitter wants a byte */ + + if ((status & I2C_IRQ_XRDY) != 0) + { + if (priv->dcnt > 0 && priv->ptr != NULL) + { + am67_i2c_traceevent(priv, I2CEVENT_SENDBYTE, priv->dcnt); + am67_i2c_putreg(priv, AM67_I2C_DATA_OFFSET, *priv->ptr++); + priv->dcnt--; + } +#ifndef CONFIG_I2C_POLLED + else + { + /* Nothing left in this segment's buffer */ + + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_CLR_OFFSET, I2C_IRQ_XRDY); + } +#endif + + am67_i2c_putreg(priv, AM67_I2C_IRQ_STAT_OFFSET, I2C_IRQ_XRDY); + } + + /* Rung 4: segment finished (ARDY), or bus released (BF). Only act if + * this segment's data really is done — in polled mode the raw status + * contains incidental BF/ARDY bits from past bus activity. + */ + + if ((status & (I2C_IRQ_ARDY | I2C_IRQ_BF)) != 0) + { + am67_i2c_putreg(priv, AM67_I2C_IRQ_STAT_OFFSET, + status & (I2C_IRQ_ARDY | I2C_IRQ_BF)); + + if (priv->dcnt <= 0 && priv->msgv != NULL) + { + if (priv->msgc > 0) + { + /* More segments: reprogram now — ARDY means the counter is + * idle and the hardware is holding the bus for us. + */ + + am67_i2c_traceevent(priv, I2CEVENT_STARTRESTART, priv->msgc); + am67_i2c_startmsg(priv); + } + else if ((raw & I2C_IRQ_BB) != 0 && + (status & I2C_IRQ_BF) == 0 && + (priv->flags & I2C_M_NOSTOP) == 0) + { + /* All data done but the bus is still held: issue the final + * STOP (legal here — this is the ARDY window) and wait for + * the bus-free event. Skip STP if a STOP is already in + * flight; hardware clears the bit when the STOP completes. + */ + + if ((am67_i2c_getreg(priv, AM67_I2C_CON_OFFSET) & + I2C_CON_STP) == 0) + { + am67_i2c_sendstop(priv); + } + +#ifndef CONFIG_I2C_POLLED + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_SET_OFFSET, I2C_IRQ_BF); +#endif + } + else + { + /* Transfer complete */ + + am67_i2c_traceevent(priv, I2CEVENT_STOP, 0); + +#ifndef CONFIG_I2C_POLLED + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_CLR_OFFSET, + I2C_ICR_CLEARMASK); + + if (priv->intstate == INTSTATE_WAITING) + { + nxsem_post(&priv->sem_isr); + priv->intstate = INTSTATE_DONE; + } +#else + priv->intstate = INTSTATE_DONE; +#endif + priv->msgv = NULL; + } + } + } + + priv->status = status; + return OK; +} + +/**************************************************************************** + * Name: am67_i2c_isr + * + * Description: + * Common I2C interrupt service routine + * + ****************************************************************************/ + +#ifndef CONFIG_I2C_POLLED +static int am67_i2c_isr(int irq, void *context, void *arg) +{ + struct am67_i2c_priv_s *priv = (struct am67_i2c_priv_s *)arg; + + DEBUGASSERT(priv != NULL); + return am67_i2c_isr_process(priv); +} +#endif + +/**************************************************************************** + * Name: am67_i2c_init + * + * Description: + * Setup the I2C hardware, ready for operation with defaults + * + ****************************************************************************/ + +static int am67_i2c_init(struct am67_i2c_priv_s *priv) +{ + /* Configure pins */ + + am67_i2c_pinmux_init(); + + /* Disable I2C module before reset */ + + am67_i2c_putreg(priv, AM67_I2C_CON_OFFSET, 0); + + /* Apply soft reset */ + + am67_i2c_putreg(priv, AM67_I2C_SYSC_OFFSET, I2C_SYSC_SRST); + + /* TI I2C requires CON.EN=1 for the internal reset to complete; + * without it RST_DONE never asserts. + */ + + am67_i2c_modifyreg(priv, AM67_I2C_CON_OFFSET, 0, I2C_CON_EN); + + while (!(am67_i2c_getreg(priv, AM67_I2C_SYSS_OFFSET) & + I2C_SYSS_RST_DONE)) + { + } + + /* No-idle mode + keep both clocks active so the WKUP domain power + * controller cannot gate WKUP_I2C0 clocks between transfers when the + * CPU enters WFI. Without this the peripheral resets between NSH + * commands and the second transfer always fails. + */ + + am67_i2c_putreg(priv, AM67_I2C_SYSC_OFFSET, + I2C_SYSC_IDLE_NO | I2C_SYSC_CLK_BOTH); + + /* Force a frequency update; setclock temporarily disables EN to write + * PSC/SCLL/SCLH, then re-enables it. + */ + + priv->frequency = 0; + am67_i2c_setclock(priv, 100000); + +#ifndef CONFIG_I2C_POLLED + /* Attach ISRs */ + + irq_attach(priv->config->irq, am67_i2c_isr, priv); + up_enable_irq(priv->config->irq); +#endif + + /* Ensure module is enabled after setclock may have re-enabled it */ + + am67_i2c_modifyreg(priv, AM67_I2C_CON_OFFSET, 0, I2C_CON_EN); + + /* Enable all wakeup sources. The TI Linux driver does this explicitly + * "to stop I2C freezing on WFI instruction" on all AM6x/J7 class + * devices. Without WE_ALL the I2C interrupt cannot wake the CPU from + * WFI, so the second transfer hangs until sem_waitdone times out. + */ + + am67_i2c_putreg(priv, AM67_I2C_WE_OFFSET, + I2C_WE_AL | I2C_WE_NACK | I2C_WE_ARDY | I2C_WE_DRDY | + I2C_WE_GC | I2C_WE_STC | I2C_WE_BF | I2C_WE_AAS | + I2C_WE_XUDF | I2C_WE_ROVR | I2C_WE_RDR | I2C_WE_XDR); + + /* Free-running mode keeps the module active under a JTAG halt */ + + am67_i2c_modifyreg(priv, AM67_I2C_SYSTEST_OFFSET, 0, I2C_SYSTEST_FREE); + + return OK; +} + +/**************************************************************************** + * Name: am67_i2c_deinit + * + * Description: + * Shutdown the I2C hardware + * + ****************************************************************************/ + +static int am67_i2c_deinit(struct am67_i2c_priv_s *priv) +{ + /* Disable I2C module */ + + am67_i2c_putreg(priv, AM67_I2C_CON_OFFSET, 0); + + /* Apply soft reset */ + + am67_i2c_putreg(priv, AM67_I2C_SYSC_OFFSET, I2C_SYSC_SRST); + + /* Disable and detach interrupts */ + +#ifndef CONFIG_I2C_POLLED + up_disable_irq(priv->config->irq); + irq_detach(priv->config->irq); +#endif + + return OK; +} + +/**************************************************************************** + * Device Driver Operations + ****************************************************************************/ + +/**************************************************************************** + * Name: am67_i2c_transfer + * + * Description: + * Generic I2C transfer function + * + ****************************************************************************/ + +static int am67_i2c_transfer(struct i2c_master_s *dev, + struct i2c_msg_s *msgs, int count) +{ + struct am67_i2c_priv_s *priv = (struct am67_i2c_priv_s *)dev; + int ret; + + DEBUGASSERT(count > 0); + + /* Ensure that address or flags don't change meanwhile */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + return ret; + } + + /* Bring the hardware up on first use. The bus is registered early during + * board bring-up (before the console), but the I2C functional clock is + * only enabled by the DM afterwards, so the reset is deferred to here. + */ + + if (!priv->inited) + { + am67_i2c_init(priv); + priv->inited = true; + } + + /* Wait for any STOP in progress */ + + ret = -EBUSY; + if (am67_i2c_sem_waitstop(priv)) + { + /* Clear TX and RX FIFOs before each transfer. Stale FIFO data from + * the previous transfer can cause XRDY/RRDY to fire at unexpected + * points in the state machine. The TI Linux driver does this too. + */ + + am67_i2c_putreg(priv, AM67_I2C_BUF_OFFSET, + I2C_BUF_TXFIFO_CLR | I2C_BUF_RXFIFO_CLR); + + /* Clear any pending error interrupts */ + + am67_i2c_putreg(priv, AM67_I2C_IRQ_STAT_OFFSET, + I2C_STS_CLEARMASK); + am67_i2c_putreg(priv, AM67_I2C_IRQ_EN_CLR_OFFSET, + I2C_ICR_CLEARMASK); + + /* Old transfers are done */ + + /* Reset ptr and dcnt to ensure an unexpected data interrupt doesn't + * overwrite stale data. + */ + + priv->dcnt = 0; + priv->ptr = NULL; + + priv->msgv = msgs; + priv->msgc = count; + + i2cinfo("Flags %x, len %d\n", msgs->flags, msgs->length); + + /* Reset I2C trace logic */ + + am67_i2c_tracereset(priv); + + /* Set I2C clock frequency (only reprograms the divisors when the + * requested frequency actually changes). + */ + + am67_i2c_setclock(priv, msgs->frequency); + + priv->status = 0; + + /* Wait for an ISR, if there was a timeout, fetch latest status to get + * the BUSY flag. + */ + + if (am67_i2c_sem_waitdone(priv) < 0) + { + ret = -ETIMEDOUT; + + i2cerr("ERROR: Timed out: IRQ_RAW: status: 0x%" PRIx32 "\n", + priv->status); + } + + /* Check for error status conditions */ + + else if ((priv->status & I2C_IRQ_ERRORMASK) != 0) + { + /* I2C_IRQ_ERRORMASK is the OR of the following individual bits: */ + + if (priv->status & I2C_IRQ_AL) + { + /* Arbitration Lost (master mode) */ + + i2cerr("Arbitration lost\n"); + ret = -EAGAIN; + } + else if (priv->status & I2C_IRQ_NACK) + { + /* Acknowledge Failure */ + + i2cerr("Ack failure\n"); + ret = -ENXIO; + } + else if (priv->status & (I2C_IRQ_XUDF | I2C_IRQ_ROVR)) + { + /* Overrun/Underrun */ + + i2cerr("Overrun/Underrun status\n"); + ret = -EIO; + } + else if (priv->status & I2C_IRQ_AERR) + { + /* Access Error in reception or transmission */ + + i2cerr("Access Error\n"); + ret = -EPROTO; + } + else if (priv->status & I2C_IRQ_BB) + { + /* Bus busy Error */ + + i2cerr("Bus busy error\n"); + ret = -EIO; + } + else + { + i2cerr("Unspecified error\n"); + ret = -EINTR; + } + } + else + { + ret = OK; + } + + /* Dump the trace result */ + + am67_i2c_tracedump(priv); + + /* Ensure ISR happening after we finish can't overwrite any user data */ + + priv->dcnt = 0; + priv->ptr = NULL; + } + else + { + i2cerr("ERROR: Bus busy, raw status: 0x%" PRIx32 "\n", + am67_i2c_getreg(priv, AM67_I2C_IRQ_STAT_RAW_OFFSET)); + } + + nxmutex_unlock(&priv->lock); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: am67_i2cbus_initialize + * + * Description: + * Initialize one I2C bus + * + ****************************************************************************/ + +struct i2c_master_s *am67_i2cbus_initialize(int port) +{ + struct am67_i2c_priv_s *priv = NULL; + + /* Get I2C private structure */ + + switch (port) + { +#ifdef CONFIG_AM67_I2C0 + case 0: + priv = (struct am67_i2c_priv_s *)&am67_i2c0_priv; + break; +#endif +#ifdef CONFIG_AM67_WKUP_I2C0 + case 2: + priv = (struct am67_i2c_priv_s *)&am67_i2c2_priv; + break; +#endif + default: + return NULL; + } + + /* Reference count only. The hardware is brought up lazily on the first + * transfer (see am67_i2c_transfer): bring-up registers the bus early, + * before the console, but the I2C functional clock is enabled by the DM + * later, so resetting the module here would spin on RST_DONE forever and + * hang boot. + */ + + nxmutex_lock(&priv->lock); + priv->refs++; + nxmutex_unlock(&priv->lock); + return (struct i2c_master_s *)priv; +} + +/**************************************************************************** + * Name: am67_i2cbus_uninitialize + * + * Description: + * Uninitialize an I2C bus + * + ****************************************************************************/ + +int am67_i2cbus_uninitialize(struct i2c_master_s *dev) +{ + struct am67_i2c_priv_s *priv = (struct am67_i2c_priv_s *)dev; + + DEBUGASSERT(dev); + + /* Decrement reference count and check for underflow */ + + if (priv->refs == 0) + { + return ERROR; + } + + nxmutex_lock(&priv->lock); + if (--priv->refs > 0) + { + nxmutex_unlock(&priv->lock); + return OK; + } + + /* Disable power and other HW resource (GPIO's), only if it was ever + * brought up (lazy init may never have run). + */ + + if (priv->inited) + { + am67_i2c_deinit(priv); + + /* Re-arm lazy bring-up so the next transfer re-runs am67_i2c_init(). */ + + priv->inited = false; + } + + nxmutex_unlock(&priv->lock); + + return OK; +} + diff --git a/arch/arm/src/am67/am67_i2c.h b/arch/arm/src/am67/am67_i2c.h new file mode 100644 index 0000000000000..460200bb98f35 --- /dev/null +++ b/arch/arm/src/am67/am67_i2c.h @@ -0,0 +1,73 @@ +/**************************************************************************** + * arch/arm/src/am67/am67_i2c.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_AM67_AM67_I2C_H +#define __ARCH_ARM_SRC_AM67_AM67_I2C_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: am67_i2cbus_initialize + * + * Description: + * Initialize the selected I2C port. And return a unique instance of struct + * struct i2c_master_s. This function may be called to obtain multiple + * instances of the interface, each of which may be set up with a + * different frequency and slave address. + * + * Input Parameters: + * Port number (for hardware that has multiple I2C interfaces) + * + * Returned Value: + * Valid I2C device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct i2c_master_s *am67_i2cbus_initialize(int port); + +/**************************************************************************** + * Name: am67_i2cbus_uninitialize + * + * Description: + * De-initialize the selected I2C port, and power down the device. + * + * Input Parameters: + * Device structure as returned by the am67_i2cbus_initialize() + * + * Returned Value: + * OK on success, ERROR when internal reference count mismatch or dev + * points to invalid hardware device. + * + ****************************************************************************/ + +int am67_i2cbus_uninitialize(struct i2c_master_s *dev); + +#endif /* __ARCH_ARM_SRC_AM67_AM67_I2C_H */ diff --git a/arch/arm/src/am67/am67_i2c_hw.h b/arch/arm/src/am67/am67_i2c_hw.h new file mode 100644 index 0000000000000..ab8ac414d13e5 --- /dev/null +++ b/arch/arm/src/am67/am67_i2c_hw.h @@ -0,0 +1,212 @@ +/**************************************************************************** + * arch/arm/src/am67/am67_i2c_hw.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_AM67_AM67_I2C_HW_H +#define __ARCH_ARM_SRC_AM67_AM67_I2C_HW_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Register offsets *********************************************************/ + +#define AM67_I2C_SYSC_OFFSET 0x0010 +#define AM67_I2C_IRQ_STAT_RAW_OFFSET 0x0024 +#define AM67_I2C_IRQ_STAT_OFFSET 0x0028 +#define AM67_I2C_IRQ_EN_SET_OFFSET 0x002c +#define AM67_I2C_IRQ_EN_CLR_OFFSET 0x0030 +#define AM67_I2C_WE_OFFSET 0x0034 +#define AM67_I2C_DMA_RX_EN_SET_OFFSET 0x0038 +#define AM67_I2C_DMA_TX_EN_SET_OFFSET 0x003c +#define AM67_I2C_DMA_RX_EN_CLR_OFFSET 0x0040 +#define AM67_I2C_DMA_TX_EN_CLR_OFFSET 0x0044 +#define AM67_I2C_DMA_RX_WAKE_EN_OFFSET 0x0048 +#define AM67_I2C_DMA_TX_WAKE_EN_OFFSET 0x004c +#define AM67_I2C_SYSS_OFFSET 0x0090 +#define AM67_I2C_BUF_OFFSET 0x0094 +#define AM67_I2C_CNT_OFFSET 0x0098 +#define AM67_I2C_DATA_OFFSET 0x009c +#define AM67_I2C_CON_OFFSET 0x00a4 +#define AM67_I2C_OA_OFFSET 0x00a8 +#define AM67_I2C_SA_OFFSET 0x00ac +#define AM67_I2C_PSC_OFFSET 0x00b0 +#define AM67_I2C_SCLL_OFFSET 0x00b4 +#define AM67_I2C_SCLH_OFFSET 0x00b8 +#define AM67_I2C_SYSTEST_OFFSET 0x00bc +#define AM67_I2C_BUFSTAT_OFFSET 0x00c0 +#define AM67_I2C_OA1_OFFSET 0x00c4 +#define AM67_I2C_OA2_OFFSET 0x00c8 +#define AM67_I2C_OA3_OFFSET 0x00cc +#define AM67_I2C_ACTOA_OFFSET 0x00d0 +#define AM67_I2C_SBLOCK_OFFSET 0x00d4 + +/* Peripheral base addresses ************************************************/ + +#define AM67_I2C0_VADDR 0x04900000 +#define AM67_WKUP_I2C0_VADDR 0x2b200000 + +/* Register bit field definitions *******************************************/ + +#define I2C_SYSC_AUTOIDLE (1 << 0) /* Bit 0: Auto-idle */ +#define I2C_SYSC_SRST (1 << 1) /* Bit 1: SoftReset */ +#define I2C_SYSC_WAKEUP (1 << 2) /* Bit 2: Enable Wakeup control */ +#define I2C_SYSC_IDLE_SHIFT (3) /* Bits 3-4: Idle Mode selection */ +#define I2C_SYSC_IDLE_MASK (3 << I2C_SYSC_IDLE_SHIFT) +# define I2C_SYSC_IDLE_FORCE (0 << I2C_SYSC_IDLE_SHIFT) /* Force-idle mode */ +# define I2C_SYSC_IDLE_NO (1 << I2C_SYSC_IDLE_SHIFT) /* No-idle mode */ +# define I2C_SYSC_IDLE_SMART (2 << I2C_SYSC_IDLE_SHIFT) /* Smart-idle mode */ +# define I2C_SYSC_IDLE_SMART_WKUP (3 << I2C_SYSC_IDLE_SHIFT) /* Smart-idle Wakeup mode */ + +#define I2C_SYSC_CLK_SHIFT (8) /* Bits 8-9: Clock Activity selection */ +#define I2C_SYSC_CLK_MASK (3 << I2C_SYSC_CLK_SHIFT) +# define I2C_SYSC_CLK_NONE (0 << I2C_SYSC_CLK_SHIFT) /* Both clocks can be cut off */ +# define I2C_SYSC_CLK_OCP (1 << I2C_SYSC_CLK_SHIFT) /* Only Interface/OCP clock must be kept active */ +# define I2C_SYSC_CLK_FUNC (2 << I2C_SYSC_CLK_SHIFT) /* Only functions clock must be kept active */ +# define I2C_SYSC_CLK_BOTH (3 << I2C_SYSC_CLK_SHIFT) /* Both clocks must be kept active */ + +#define I2C_IRQ_AL (1 << 0) /* Bit 0: Arbitration lost */ +#define I2C_IRQ_NACK (1 << 1) /* Bit 1: No acknowledgment */ +#define I2C_IRQ_ARDY (1 << 2) /* Bit 2: Register access ready */ +#define I2C_IRQ_RRDY (1 << 3) /* Bit 3: Receive data ready */ +#define I2C_IRQ_XRDY (1 << 4) /* Bit 4: Transmit data ready */ +#define I2C_IRQ_GC (1 << 5) /* Bit 5: General call */ +#define I2C_IRQ_STC (1 << 6) /* Bit 6: Start Condition */ +#define I2C_IRQ_AERR (1 << 7) /* Bit 7: Access Error */ +#define I2C_IRQ_BF (1 << 8) /* Bit 8: Bus Free */ +#define I2C_IRQ_AAS (1 << 9) /* Bit 9: Address recognized as slave */ +#define I2C_IRQ_XUDF (1 << 10) /* Bit 10: Transmit underflow */ +#define I2C_IRQ_ROVR (1 << 11) /* Bit 11: Receive overrun */ +#define I2C_IRQ_BB (1 << 12) /* Bit 12: Bus busy */ +#define I2C_IRQ_RDR (1 << 13) /* Bit 13: Receive draining IRQ */ +#define I2C_IRQ_XDR (1 << 14) /* Bit 14: Transmit draining IRQ */ + +#define I2C_IRQ_ERRORMASK (I2C_IRQ_AL | I2C_IRQ_NACK | I2C_IRQ_AERR | I2C_IRQ_XUDF | I2C_IRQ_ROVR) + +#define I2C_STS_CLEARMASK (I2C_IRQ_AL | I2C_IRQ_NACK | I2C_IRQ_ARDY | I2C_IRQ_RRDY | I2C_IRQ_XRDY \ + | I2C_IRQ_GC | I2C_IRQ_STC | I2C_IRQ_AERR | I2C_IRQ_BF | I2C_IRQ_AAS \ + | I2C_IRQ_XUDF | I2C_IRQ_ROVR | I2C_IRQ_BB | I2C_IRQ_RDR | I2C_IRQ_XDR) + +#define I2C_ICR_CLEARMASK (I2C_IRQ_AL | I2C_IRQ_NACK | I2C_IRQ_ARDY | I2C_IRQ_RRDY | I2C_IRQ_XRDY \ + | I2C_IRQ_GC | I2C_IRQ_STC | I2C_IRQ_AERR | I2C_IRQ_BF | I2C_IRQ_AAS \ + | I2C_IRQ_XUDF | I2C_IRQ_ROVR | I2C_IRQ_RDR | I2C_IRQ_XDR) + +#define I2C_WE_AL (1 << 0) /* Bit 0: Arbitration lost */ +#define I2C_WE_NACK (1 << 1) /* Bit 1: No acknowledgment */ +#define I2C_WE_ARDY (1 << 2) /* Bit 2: Register access ready */ +#define I2C_WE_DRDY (1 << 3) /* Bit 3: Receive/Transmit data ready */ +#define I2C_WE_GC (1 << 5) /* Bit 5: General call */ +#define I2C_WE_STC (1 << 6) /* Bit 6: Start Condition */ +#define I2C_WE_BF (1 << 8) /* Bit 8: Bus Free */ +#define I2C_WE_AAS (1 << 9) /* Bit 9: Address recognized as slave */ +#define I2C_WE_XUDF (1 << 10) /* Bit 10: Transmit underflow */ +#define I2C_WE_ROVR (1 << 11) /* Bit 11: Receive overrun */ +#define I2C_WE_RDR (1 << 13) /* Bit 13: Receive draining IRQ */ +#define I2C_WE_XDR (1 << 14) /* Bit 14: Transmit draining IRQ */ + +#define I2C_DMA_ENABLE (1 << 0) /* Bit 0: DMA channel enable */ + +#define I2C_SYSS_RST_DONE (1 << 0) /* Bit 0: Reset done */ + +#define I2C_BUF_TXTRSH_SHIFT (0) /* Bits 0-5: Threshold value for FIFO buffer in TX mode */ +#define I2C_BUF_TXTRSH_MASK (63 << I2C_BUF_TXTRSH_SHIFT) +#define I2C_BUF_TXFIFO_CLR (1 << 6) /* Bit 6: Transmit FIFO clear */ +#define I2C_BUF_XDMA_EN (1 << 7) /* Bit 7: Transmit DMA channel enable */ + +#define I2C_BUF_RXTRSH_SHIFT (8) /* Bits 8-13: Threshold value for FIFO buffer in RX mode */ +#define I2C_BUF_RXTRSH_MASK (63 << I2C_BUF_RXTRSH_SHIFT) +#define I2C_BUF_RXFIFO_CLR (1 << 14) /* Bit 14: Receive FIFO clear */ +#define I2C_BUF_RDMA_EN (1 << 15) /* Bit 15: Receive DMA channel enable */ + +#define I2C_CNT_SHIFT (0) /* Bits 0-15: Data count */ +#define I2C_CNT_MASK (65535 << I2C_CNT_SHIFT) + +#define I2C_DATA_SHIFT (0) /* Bits 0-7: Transmit/Receive data FIFO endpoint */ +#define I2C_DATA_MASK (255 << I2C_DATA_SHIFT) + +#define I2C_CON_STT (1 << 0) /* Bit 0: Start condition (I2C master mode only) */ +#define I2C_CON_STP (1 << 1) /* Bit 1: Stop condition (I2C master mode only) */ +#define I2C_CON_XOA3 (1 << 4) /* Bit 4: Expand own address 3 */ +#define I2C_CON_XOA2 (1 << 5) /* Bit 5: Expand own address 2 */ +#define I2C_CON_XOA1 (1 << 6) /* Bit 6: Expand own address 1 */ +#define I2C_CON_XOA0 (1 << 7) /* Bit 7: Expand own address 0 */ +#define I2C_CON_XSA (1 << 8) /* Bit 8: Expand slave address */ +#define I2C_CON_TRX (1 << 9) /* Bit 9: Transmitter/receiver mode (I2C master mode only) */ +#define I2C_CON_MST (1 << 10) /* Bit 10: Master/slave mode */ +#define I2C_CON_STB (1 << 11) /* Bit 11: Start byte mode (I2C master mode only) */ +#define I2C_CON_OPMODE_SHIFT (12) /* Bits 12-13: Operation mode selection */ +#define I2C_CON_OPMODE_MASK (3 << I2C_CON_OPMODE_SHIFT) +# define I2C_CON_OPMODE_FAST (0 << I2C_CON_OPMODE_SHIFT) +#define I2C_CON_EN (1 << 15) /* Bit 15: I2C module enable */ + +#define I2C_SA_SHIFT (0) /* Bits 0-9: Slave address */ +#define I2C_SA_MASK (0x3ff << I2C_SA_SHIFT) + +#define I2C_PSC_SHIFT (0) /* Bits 0-7: Fast/Standard mode prescale sampling clock divider */ +#define I2C_PSC_MASK (255 << I2C_PSC_SHIFT) + +#define I2C_SCLL_SHIFT (0) /* Bits 0-7: Fast/Standard mode SCL low time */ +#define I2C_SCLL_MASK (255 << I2C_SCLL_SHIFT) + +#define I2C_SCLH_SHIFT (0) /* Bits 0-7: Fast/Standard mode SCL high time. */ +#define I2C_SCLH_MASK (255 << I2C_SCLH_SHIFT) + +#define I2C_SYSTEST_SDA_O (1 << 0) /* Bit 0: SDA line drive output value */ +#define I2C_SYSTEST_SDA_I (1 << 1) /* Bit 1: SDA line sense input value */ +#define I2C_SYSTEST_SCL_O (1 << 2) /* Bit 2: SCL line drive output value */ +#define I2C_SYSTEST_SCL_I (1 << 3) /* Bit 3: SCL line sense input value */ +#define I2C_SYSTEST_SDA_O_FUNC (1 << 5) /* Bit 5: SDA line output value (functional mode) */ +#define I2C_SYSTEST_SDA_I_FUNC (1 << 6) /* Bit 6: SDA line input value (functional mode) */ +#define I2C_SYSTEST_SCL_O_FUNC (1 << 7) /* Bit 7: SCL line output value (functional mode) */ +#define I2C_SYSTEST_SCL_I_FUNC (1 << 8) /* Bit 8: SCL line input value (functional mode) */ +#define I2C_SYSTEST_SSB (1 << 11) /* Bit 11: Set status bits */ + +#define I2C_SYSTEST_TMODE_SHIFT (12) /* Bits 12-13: Test mode select */ +#define I2C_SYSTEST_TMODE_MASK (3 << I2C_SYSTEST_TMODE_SHIFT) +# define I2C_SYSTEST_TMODE_FUNC (0 << I2C_SYSTEST_TMODE_SHIFT) /* Functional mode */ +# define I2C_SYSTEST_TMODE_SCL (2 << I2C_SYSTEST_TMODE_SHIFT) /* Test of SCL counters (SCLL, SCLH, PSC) */ +# define I2C_SYSTEST_TMODE_LOOPBACK (3 << I2C_SYSTEST_TMODE_SHIFT) /* Loop back mode select + SDA/SCL IO mode select */ + +#define I2C_SYSTEST_FREE (1 << 14) /* Bit 14: Free running mode (on breakpoint) */ +#define I2C_SYSTEST_ST_EN (1 << 15) /* Bit 15: System test enable */ + +#define I2C_BUFSTAT_TXSTAT_SHIFT (0) /* Bits 0-5: TX buffer status */ +#define I2C_BUFSTAT_TXSTAT_MASK (63 << I2C_BUFSTAT_TXSTAT_SHIFT) +#define I2C_BUFSTAT_RXSTAT_SHIFT (8) /* Bits 8-13: RX buffer status */ +#define I2C_BUFSTAT_RXSTAT_MASK (63 << I2C_BUFSTAT_RXSTAT_SHIFT) +#define I2C_BUFSTAT_FIFODEPTH_SHIFT (14) /* Bits 14-15: Internal FIFO buffers depth */ +#define I2C_BUFSTAT_FIFODEPTH_MASK (3 << I2C_BUFSTAT_FIFODEPTH_SHIFT) + +#define I2C_OA_SHIFT (0) /* Bits 0-9: Own address */ +#define I2C_OA_MASK (0x3ff << I2C_OA_SHIFT) + +#define I2C_OA0_SELECT (1 << 0) /* Bit 0: Own address 0 */ +#define I2C_OA1_SELECT (1 << 1) /* Bit 1: Own address 1 */ +#define I2C_OA2_SELECT (1 << 2) /* Bit 2: Own address 2 */ +#define I2C_OA3_SELECT (1 << 3) /* Bit 3: Own address 3 */ + +#endif /* __ARCH_ARM_SRC_AM67_AM67_I2C_HW_H */ diff --git a/arch/arm/src/am67/am67_pinmux.h b/arch/arm/src/am67/am67_pinmux.h index 32a941e526927..cc336a89ccda5 100644 --- a/arch/arm/src/am67/am67_pinmux.h +++ b/arch/arm/src/am67/am67_pinmux.h @@ -314,5 +314,6 @@ void am67_mcu_pinmux_config(const struct pinmux_conf_s *pinmux_conf); void am67_pinmux_init(void); void am67_spi_pinmux_init(void); +void am67_i2c_pinmux_init(void); #endif /* __ARCH_ARM_SRC_AM67_AM67_PINMUX_H */ diff --git a/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig b/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig index bb1161f5b32bf..6504186a010f3 100644 --- a/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig +++ b/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig @@ -14,7 +14,9 @@ CONFIG_16550_UART0_CLOCK=48000000 CONFIG_16550_UART0_IRQ=211 CONFIG_16550_UART0_SERIAL_CONSOLE=y CONFIG_16550_UART=y +CONFIG_AM67_I2C=y CONFIG_AM67_MCSPI0=y +CONFIG_AM67_WKUP_I2C0=y CONFIG_ARCH="arm" CONFIG_ARCH_BOARD="t3-gem-o1" CONFIG_ARCH_BOARD_T3_GEM_O1=y @@ -31,6 +33,7 @@ CONFIG_FS_LINKS=y CONFIG_FS_PROCFS=y CONFIG_HAVE_CXX=y CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_I2C=y CONFIG_INIT_ENTRYPOINT="nsh_main" CONFIG_NSH_BUILTIN_APPS=y CONFIG_NSH_FILEIOSIZE=512 @@ -47,6 +50,7 @@ CONFIG_SCHED_HAVE_PARENT=y CONFIG_SCHED_WAITPID=y CONFIG_SIG_DEFAULT=y CONFIG_SPI=y +CONFIG_SYSTEM_I2CTOOL=y CONFIG_SYSTEM_NSH=y CONFIG_SYSTEM_SPITOOL=y CONFIG_TESTING_OSTEST=y diff --git a/boards/arm/am67/t3-gem-o1/src/Makefile b/boards/arm/am67/t3-gem-o1/src/Makefile index eb4baa5b7c5ab..2adc979343503 100644 --- a/boards/arm/am67/t3-gem-o1/src/Makefile +++ b/boards/arm/am67/t3-gem-o1/src/Makefile @@ -29,4 +29,8 @@ CSRCS += am67_spi.c CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/arch/arm/src/am67 endif +ifeq ($(CONFIG_AM67_I2C),y) +CSRCS += am67_i2c.c +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/arch/arm/src/am67 +endif include $(TOPDIR)/boards/Board.mk diff --git a/boards/arm/am67/t3-gem-o1/src/am67_bringup.c b/boards/arm/am67/t3-gem-o1/src/am67_bringup.c index dea600d17f269..461956e98fb52 100644 --- a/boards/arm/am67/t3-gem-o1/src/am67_bringup.c +++ b/boards/arm/am67/t3-gem-o1/src/am67_bringup.c @@ -35,6 +35,10 @@ #include "am67_gpio.h" #endif +#if defined(CONFIG_AM67_I2C0) || defined(CONFIG_AM67_WKUP_I2C0) +#include "am67_i2c.h" +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -63,6 +67,10 @@ int am67_bringup(void) am67_spidev_initialize(); #endif +#if defined(CONFIG_AM67_I2C0) || defined(CONFIG_AM67_WKUP_I2C0) + am67_i2cdev_initialize(); +#endif + #ifdef CONFIG_FS_PROCFS /* Mount the procfs file system */ diff --git a/boards/arm/am67/t3-gem-o1/src/am67_i2c.c b/boards/arm/am67/t3-gem-o1/src/am67_i2c.c new file mode 100644 index 0000000000000..fd4e1c0181f68 --- /dev/null +++ b/boards/arm/am67/t3-gem-o1/src/am67_i2c.c @@ -0,0 +1,79 @@ +/**************************************************************************** + * boards/arm/am67/t3-gem-o1/src/am67_i2c.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include "am67_i2c.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: am67_i2cdev_initialize + ****************************************************************************/ + +void am67_i2cdev_initialize(void) +{ + FAR struct i2c_master_s *i2c; + int ret; + +#ifdef CONFIG_AM67_I2C0 + i2c = am67_i2cbus_initialize(0); + if (i2c == NULL) + { + i2cerr("ERROR: Failed to initialize I2C0\n"); + } + else + { + ret = i2c_register(i2c, 0); + if (ret < 0) + { + i2cerr("ERROR: Failed to register /dev/i2c0: %d\n", ret); + } + } +#endif + +#ifdef CONFIG_AM67_WKUP_I2C0 + i2c = am67_i2cbus_initialize(2); + if (i2c == NULL) + { + i2cerr("ERROR: Failed to initialize WKUP_I2C0\n"); + } + else + { + ret = i2c_register(i2c, 2); + if (ret < 0) + { + i2cerr("ERROR: Failed to register /dev/i2c2: %d\n", ret); + } + } +#endif +} + diff --git a/boards/arm/am67/t3-gem-o1/src/t3-gem-o1.h b/boards/arm/am67/t3-gem-o1/src/t3-gem-o1.h index ad977b38ce589..948bace4a33ba 100644 --- a/boards/arm/am67/t3-gem-o1/src/t3-gem-o1.h +++ b/boards/arm/am67/t3-gem-o1/src/t3-gem-o1.h @@ -46,5 +46,9 @@ uint8_t am67_spi0status(FAR struct spi_dev_s *dev, uint32_t devid); void am67_spidev_initialize(void); #endif +#if defined(CONFIG_AM67_I2C0) || defined(CONFIG_AM67_WKUP_I2C0) +void am67_i2cdev_initialize(void); +#endif + #endif /* __ASSEMBLY__ */ #endif /* __BOARDS_ARM_T3_GEM_O1_SRC_T3_GEM_O1_H */