| /**************************************************************************** |
| * arch/mips/src/pic32mz/pic32mz_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 <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/clock.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/semaphore.h> |
| #include <nuttx/i2c/i2c_master.h> |
| |
| #include <arch/board/board.h> |
| |
| #include "mips_internal.h" |
| #include "hardware/pic32mz_i2c.h" |
| #include "pic32mz_i2c.h" |
| |
| /* At least one I2C peripheral must be enabled */ |
| |
| #ifdef CONFIG_PIC32MZ_I2C |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* 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_PIC32MZ_I2CTIMEOSEC) && !defined(CONFIG_PIC32MZ_I2CTIMEOMS) |
| # define CONFIG_PIC32MZ_I2CTIMEOSEC 0 |
| # define CONFIG_PIC32MZ_I2CTIMEOMS 500 /* Default is 500 milliseconds */ |
| #elif !defined(CONFIG_PIC32MZ_I2CTIMEOSEC) |
| # define CONFIG_PIC32MZ_I2CTIMEOSEC 0 /* User provided milliseconds */ |
| #elif !defined(CONFIG_PIC32MZ_I2CTIMEOMS) |
| # define CONFIG_PIC32MZ_I2CTIMEOMS 0 /* User provided seconds */ |
| #endif |
| |
| /* Interrupt wait time timeout in system timer ticks */ |
| |
| #ifndef CONFIG_PIC32MZ_I2CTIMEOTICKS |
| # define CONFIG_PIC32MZ_I2CTIMEOTICKS \ |
| (SEC2TICK(CONFIG_PIC32MZ_I2CTIMEOSEC) +\ |
| MSEC2TICK(CONFIG_PIC32MZ_I2CTIMEOMS)) |
| #endif |
| |
| #ifndef CONFIG_PIC32MZ_I2C_DYNTIMEO_STARTSTOP |
| # define CONFIG_PIC32MZ_I2C_DYNTIMEO_STARTSTOP \ |
| TICK2USEC(CONFIG_PIC32MZ_I2CTIMEOTICKS) |
| #endif |
| |
| /* Debug ********************************************************************/ |
| |
| /* I2C event trace logic. */ |
| |
| #ifndef CONFIG_I2C_TRACE |
| # define pic32mz_i2c_tracereset(p) |
| # define pic32mz_i2c_tracenew(p,s) |
| # define pic32mz_i2c_traceevent(p,e,a) |
| # define pic32mz_i2c_tracedump(p) |
| #endif |
| |
| #ifndef CONFIG_I2C_NTRACE |
| # define CONFIG_I2C_NTRACE 32 |
| #endif |
| |
| #if defined(CONFIG_I2C_TRACE) && defined(CONFIG_I2C_POLLED) |
| # warning Default trace events might not be enough during polling. |
| #endif |
| |
| #ifdef CONFIG_I2C_SLAVE |
| # error I2C slave logic is not supported yet for PIC32MZ |
| #endif |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* Interrupt state */ |
| |
| enum pic32mz_intstate_e |
| { |
| INTSTATE_IDLE = 0, /* No I2C activity */ |
| INTSTATE_WAITING, /* Waiting for completion of interrupt activity */ |
| INTSTATE_DONE, /* Interrupt activity complete */ |
| }; |
| |
| /* Process state */ |
| |
| enum pic32mz_process_state_e |
| { |
| PROCESS_STATE_SEND_ADDR = 0, |
| PROCESS_STATE_SEND_DATA, |
| PROCESS_STATE_ENABLE_READ, |
| PROCESS_STATE_READ_DATA, |
| PROCESS_STATE_FETCH_NEXT, |
| PROCESS_STATE_TRANSFERT_DONE |
| }; |
| |
| /* Trace events */ |
| |
| enum pic32mz_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_RCVMODEEN, /* Receive mode enabled, param = 0 */ |
| I2CEVENT_RCVBYTE, /* Read more data, param = dcnt */ |
| I2CEVENT_NOSTART, /* BTF on last byte with no restart, param = dcnt */ |
| I2CEVENT_STARTRESTART, /* Last byte sent, re-starting, param = 0 */ |
| I2CEVENT_STOP, /* Last byte sten, send stop, param = 0 */ |
| I2CEVENT_WAKEUP, /* Everything is done, wake up caller, param = 0 */ |
| I2CEVENT_ERROR /* Bus collision error, param = 0 */ |
| }; |
| |
| /* Trace data */ |
| |
| struct pic32mz_trace_s |
| { |
| uint32_t status; /* I2C 32-bit SR2|SR1 status */ |
| uint32_t count; /* Interrupt count when status change */ |
| enum pic32mz_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 pic32mz_i2c_config_s |
| { |
| uint32_t base; /* I2C base address */ |
| uint32_t scl_pin; /* GPIO configuration for SCL as SCL */ |
| uint32_t sda_pin; /* GPIO configuration for SDA as SDA */ |
| uint8_t mode; /* Master or Slave mode */ |
| #ifndef CONFIG_I2C_POLLED |
| uint32_t ev_irq; /* Event IRQ */ |
| uint32_t er_irq; /* Error IRQ */ |
| #endif |
| }; |
| |
| /* I2C Device Private Data */ |
| |
| struct pic32mz_i2c_priv_s |
| { |
| /* Standard I2C operations */ |
| |
| const struct i2c_ops_s *ops; |
| |
| /* Port configuration */ |
| |
| const struct pic32mz_i2c_config_s *config; |
| |
| int refs; /* Reference count */ |
| 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 |
| * pic32mz_intstate_e) */ |
| volatile uint8_t process_state; /* State of the isr process */ |
| |
| 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 pic32mz_trace_s trace[CONFIG_I2C_NTRACE]; |
| #endif |
| |
| uint32_t status; /* End of transfer status */ |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static inline uint32_t |
| pic32mz_i2c_getreg(struct pic32mz_i2c_priv_s *priv, |
| uint8_t offset); |
| static inline void pic32mz_i2c_putreg(struct pic32mz_i2c_priv_s *priv, |
| uint8_t offset, uint32_t value); |
| static inline void pic32mz_i2c_modifyreg(struct pic32mz_i2c_priv_s *priv, |
| uint8_t offset, uint32_t clearbits, |
| uint32_t setbits); |
| |
| #ifdef CONFIG_PICM32MZ_I2C_DYNTIMEO |
| static uint32_t pic32mz_i2c_toticks(int msgc, struct i2c_msg_s *msgs); |
| #endif /* CONFIG_PIC32MZ_I2C_DYNTIMEO */ |
| |
| static inline int |
| pic32mz_i2c_sem_waitdone(struct pic32mz_i2c_priv_s *priv); |
| static inline void |
| pic32mz_i2c_sem_waitidle(struct pic32mz_i2c_priv_s *priv); |
| |
| #ifdef CONFIG_I2C_TRACE |
| static void pic32mz_i2c_tracereset(struct pic32mz_i2c_priv_s *priv); |
| static void pic32mz_i2c_tracenew(struct pic32mz_i2c_priv_s *priv, |
| uint32_t status); |
| static void |
| pic32mz_i2c_traceevent(struct pic32mz_i2c_priv_s *priv, |
| enum pic32mz_trace_e event, uint32_t parm); |
| static void pic32mz_i2c_tracedump(struct pic32mz_i2c_priv_s *priv); |
| #endif /* CONFIG_I2C_TRACE */ |
| |
| static inline int |
| pic32mz_i2c_setbaudrate(struct pic32mz_i2c_priv_s *priv, |
| uint32_t frequency); |
| static inline void |
| pic32mz_i2c_send_start(struct pic32mz_i2c_priv_s *priv); |
| static inline void |
| pic32mz_i2c_send_stop(struct pic32mz_i2c_priv_s *priv); |
| static inline void |
| pic32mz_i2c_send_repeatedstart(struct pic32mz_i2c_priv_s *priv); |
| static inline void pic32mz_i2c_send_ack(struct pic32mz_i2c_priv_s *priv, |
| bool ack); |
| static inline void pic32mz_i2c_transmitbyte(struct pic32mz_i2c_priv_s *priv, |
| uint8_t data); |
| static inline uint32_t |
| pic32mz_i2c_receivebyte(struct pic32mz_i2c_priv_s *priv); |
| |
| static inline uint32_t |
| pic32mz_i2c_getstatus(struct pic32mz_i2c_priv_s *priv); |
| static inline bool |
| pic32mz_i2c_master_inactive(struct pic32mz_i2c_priv_s *priv); |
| |
| static int pic32mz_i2c_isr_process(struct pic32mz_i2c_priv_s *priv); |
| |
| #ifndef CONFIG_I2C_POLLED |
| static int pic32mz_i2c_isr(int irq, void *context, void *arg); |
| #endif /* !CONFIG_I2C_POLLED */ |
| |
| static int pic32mz_i2c_init(struct pic32mz_i2c_priv_s *priv); |
| static int pic32mz_i2c_deinit(struct pic32mz_i2c_priv_s *priv); |
| |
| static int pic32mz_i2c_transfer(struct i2c_master_s *dev, |
| struct i2c_msg_s *msgs, int count); |
| #ifdef CONFIG_I2C_RESET |
| static int pic32mz_i2c_reset(struct i2c_master_s *dev); |
| #endif |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Trace events strings */ |
| |
| #ifdef CONFIG_I2C_TRACE |
| static const char *g_trace_names[] = |
| { |
| "NONE ", |
| "SENDADDR ", |
| "SENDBYTE ", |
| "RCVMODEEN ", |
| "RCVBYTE ", |
| "NOSTART ", |
| "RESTART ", |
| "STOP ", |
| "WAKEUP ", |
| "ERROR " |
| }; |
| #endif |
| |
| /* I2C interface */ |
| |
| static const struct i2c_ops_s pic32mz_i2c_ops = |
| { |
| .transfer = pic32mz_i2c_transfer, |
| #ifdef CONFIG_I2C_RESET |
| .reset = pic32mz_i2c_reset, |
| #endif |
| }; |
| |
| /* I2C device structures */ |
| |
| #ifdef CONFIG_PIC32MZ_I2C1 |
| static const struct pic32mz_i2c_config_s pic32mz_i2c1_config = |
| { |
| .base = PIC32MZ_I2C1_K1BASE, |
| .scl_pin = GPIO_I2C1_SCL, |
| .sda_pin = GPIO_I2C1_SDA, |
| #ifndef CONFIG_I2C_POLLED |
| .ev_irq = PIC32MZ_IRQ_I2C1M, |
| .er_irq = PIC32MZ_IRQ_I2C1COL |
| #endif |
| }; |
| |
| static struct pic32mz_i2c_priv_s pic32mz_i2c1_priv = |
| { |
| .ops = &pic32mz_i2c_ops, |
| .config = &pic32mz_i2c1_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, |
| .frequency = 0, |
| .dcnt = 0, |
| .flags = 0, |
| .status = 0, |
| }; |
| #endif |
| |
| #ifdef CONFIG_PIC32MZ_I2C2 |
| static const struct pic32mz_i2c_config_s pic32mz_i2c2_config = |
| { |
| .base = PIC32MZ_I2C2_K1BASE, |
| .scl_pin = GPIO_I2C2_SCL, |
| .sda_pin = GPIO_I2C2_SDA, |
| #ifndef CONFIG_I2C_POLLED |
| .ev_irq = PIC32MZ_IRQ_I2C2M, |
| .er_irq = PIC32MZ_IRQ_I2C2COL |
| #endif |
| }; |
| |
| static struct pic32mz_i2c_priv_s pic32mz_i2c2_priv = |
| { |
| .ops = &pic32mz_i2c_ops, |
| .config = &pic32mz_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, |
| .frequency = 0, |
| .dcnt = 0, |
| .flags = 0, |
| .status = 0, |
| }; |
| #endif |
| |
| #ifdef CONFIG_PIC32MZ_I2C3 |
| static const struct pic32mz_i2c_config_s pic32mz_i2c3_config = |
| { |
| .base = PIC32MZ_I2C3_K1BASE, |
| .scl_pin = GPIO_I2C3_SCL, |
| .sda_pin = GPIO_I2C3_SDA, |
| #ifndef CONFIG_I2C_POLLED |
| .ev_irq = PIC32MZ_IRQ_I2C3M, |
| .er_irq = PIC32MZ_IRQ_I2C3COL |
| #endif |
| }; |
| |
| static struct pic32mz_i2c_priv_s pic32mz_i2c3_priv = |
| { |
| .ops = &pic32mz_i2c_ops, |
| .config = &pic32mz_i2c3_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, |
| .frequency = 0, |
| .dcnt = 0, |
| .flags = 0, |
| .status = 0, |
| }; |
| #endif |
| |
| #ifdef CONFIG_PIC32MZ_I2C4 |
| static const struct pic32mz_i2c_config_s pic32mz_i2c4_config = |
| { |
| .base = PIC32MZ_I2C4_K1BASE, |
| .scl_pin = GPIO_I2C4_SCL, |
| .sda_pin = GPIO_I2C4_SDA, |
| #ifndef CONFIG_I2C_POLLED |
| .ev_irq = PIC32MZ_IRQ_I2C4M, |
| .er_irq = PIC32MZ_IRQ_I2C4COL |
| #endif |
| }; |
| |
| static struct pic32mz_i2c_priv_s pic32mz_i2c4_priv = |
| { |
| .ops = &pic32mz_i2c_ops, |
| .config = &pic32mz_i2c4_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, |
| .frequency = 0, |
| .dcnt = 0, |
| .flags = 0, |
| .status = 0, |
| }; |
| #endif |
| |
| #ifdef CONFIG_PIC32MZ_I2C5 |
| static const struct pic32mz_i2c_config_s pic32mz_i2c5_config = |
| { |
| .base = PIC32MZ_I2C5_K1BASE, |
| .scl_pin = GPIO_I2C5_SCL, |
| .sda_pin = GPIO_I2C5_SDA, |
| #ifndef CONFIG_I2C_POLLED |
| .ev_irq = PIC32MZ_IRQ_I2C5M, |
| .er_irq = PIC32MZ_IRQ_I2C5COL |
| #endif |
| }; |
| |
| static struct pic32mz_i2c_priv_s pic32mz_i2c5_priv = |
| { |
| .ops = &pic32mz_i2c_ops, |
| .config = &pic32mz_i2c5_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, |
| .frequency = 0, |
| .dcnt = 0, |
| .flags = 0, |
| .status = 0, |
| }; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_I2C_TRACE |
| static void pic32mz_i2c_traceclear(struct pic32mz_i2c_priv_s *priv) |
| { |
| struct pic32mz_trace_s *trace = &priv->trace[priv->tndx]; |
| |
| trace->status = 0; /* I2C 32-bit 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 pic32mz_i2c_tracereset(struct pic32mz_i2c_priv_s *priv) |
| { |
| /* Reset the trace info for a new data collection */ |
| |
| priv->tndx = 0; |
| priv->start_time = clock_systime_ticks(); |
| pic32mz_i2c_traceclear(priv); |
| } |
| |
| static void pic32mz_i2c_tracenew(struct pic32mz_i2c_priv_s *priv, |
| uint32_t status) |
| { |
| struct pic32mz_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 */ |
| |
| pic32mz_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 pic32mz_i2c_traceevent(struct pic32mz_i2c_priv_s *priv, |
| enum pic32mz_trace_e event, uint32_t parm) |
| { |
| struct pic32mz_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++; |
| pic32mz_i2c_traceclear(priv); |
| } |
| } |
| |
| static void pic32mz_i2c_tracedump(struct pic32mz_i2c_priv_s *priv) |
| { |
| struct pic32mz_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: %04x 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: pic32mz_i2c_getreg |
| * |
| * Description: |
| * Get a 32-bit register value by offset |
| * |
| ****************************************************************************/ |
| |
| static inline uint32_t |
| pic32mz_i2c_getreg(struct pic32mz_i2c_priv_s *priv, uint8_t offset) |
| { |
| return getreg32(priv->config->base + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_putreg |
| * |
| * Description: |
| * Put a 32-bit register value by offset |
| * |
| ****************************************************************************/ |
| |
| static inline void pic32mz_i2c_putreg(struct pic32mz_i2c_priv_s *priv, |
| uint8_t offset, uint32_t value) |
| { |
| putreg32(value, priv->config->base + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_modifyreg |
| * |
| * Description: |
| * Modify a 32-bit register value by offset |
| * |
| ****************************************************************************/ |
| |
| static inline void pic32mz_i2c_modifyreg(struct pic32mz_i2c_priv_s *priv, |
| uint8_t offset, uint32_t clearbits, |
| uint32_t setbits) |
| { |
| modifyreg32(priv->config->base + offset, clearbits, setbits); |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_toticks |
| * |
| * Description: |
| * Return a micro-second delay based on the number of bytes left to be |
| * processed. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_PIC32MZ_I2C_DYNTIMEO |
| static uint32_t pic32mz_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_PIC32MZ_I2C_DYNTIMEO_USECPERBYTE * bytecount); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_sem_waitdone |
| * |
| * Description: |
| * Wait for a transfer to complete |
| * |
| * There are two versions of this function. The first is included when using |
| * interrupts while the second is used if polling (CONFIG_I2C_POLLED=y). |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_I2C_POLLED |
| static inline int |
| pic32mz_i2c_sem_waitdone(struct pic32mz_i2c_priv_s *priv) |
| { |
| irqstate_t flags; |
| int ret; |
| |
| flags = enter_critical_section(); |
| |
| /* Signal the interrupt handler that we are waiting */ |
| |
| priv->intstate = INTSTATE_WAITING; |
| |
| do |
| { |
| /* Wait until either the transfer is complete or the timeout expires */ |
| |
| #ifdef CONFIG_PIC32MZ_I2C_DYNTIMEO |
| ret = nxsem_tickwait_uninterruptible(&priv->sem_isr, |
| pic32mz_i2c_toticks(priv->msgc, priv->msgv)); |
| #else |
| ret = nxsem_tickwait_uninterruptible(&priv->sem_isr, |
| CONFIG_PIC32MZ_I2CTIMEOTICKS); |
| #endif |
| if (ret < 0) |
| { |
| /* Break out of the loop on irrecoverable errors. This would |
| * include timeouts and mystery errors reported by |
| * nxsem_tickwait_uninterruptible. |
| */ |
| |
| 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. */ |
| |
| up_disable_irq(priv->config->ev_irq); |
| up_disable_irq(priv->config->er_irq); |
| |
| leave_critical_section(flags); |
| |
| return ret; |
| } |
| #else |
| static inline int |
| pic32mz_i2c_sem_waitdone(struct pic32mz_i2c_priv_s *priv) |
| { |
| clock_t timeout; |
| clock_t start; |
| clock_t elapsed; |
| int ret; |
| |
| /* Get the timeout value */ |
| |
| #ifdef CONFIG_PIC32MZ_I2C_DYNTIMEO |
| timeout = pic32mz_i2c_toticks(priv->msgc, priv->msgv); |
| #else |
| timeout = CONFIG_PIC32MZ_I2CTIMEOTICKS; |
| #endif |
| |
| /* Signal the interrupt handler that we are waiting. NOTE: Interrupts |
| * are currently disabled but will be temporarily re-enabled below when |
| * nxsem_tickwait_uninterruptible() sleeps. |
| */ |
| |
| priv->intstate = INTSTATE_WAITING; |
| 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. |
| */ |
| |
| pic32mz_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: %04x\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: pic32mz_i2c_sem_waitidle |
| * |
| * Description: |
| * Wait for the bus to be in idle. |
| * |
| ****************************************************************************/ |
| |
| static inline void |
| pic32mz_i2c_sem_waitidle(struct pic32mz_i2c_priv_s *priv) |
| { |
| uint32_t timeout; |
| uint32_t start; |
| uint32_t elapsed; |
| uint32_t con; |
| uint32_t stat; |
| |
| /* Select a timeout */ |
| |
| #ifdef CONFIG_PIC32MZ_I2C_DYNTIMEO |
| timeout = USEC2TICK(CONFIG_PIC32MZ_I2C_DYNTIMEO_STARTSTOP); |
| #else |
| timeout = CONFIG_PIC32MZ_I2CTIMEOTICKS; |
| #endif |
| |
| start = clock_systime_ticks(); |
| do |
| { |
| elapsed = clock_systime_ticks() - start; |
| |
| /* The bus is idle if the five least significant bits of I2CxCON |
| * are cleared and the I2CxSTAT<TRSTAT> flag is cleared. |
| */ |
| |
| con = pic32mz_i2c_getreg(priv, PIC32MZ_I2C_CON_OFFSET); |
| stat = pic32mz_i2c_getreg(priv, PIC32MZ_I2C_STAT_OFFSET); |
| if (((con & I2C_CON_IDLEMASK) == 0) && ((stat & I2C_STAT_TRSTAT) == 0)) |
| { |
| return; |
| } |
| } |
| while (elapsed < timeout); |
| |
| /* If we get here then a timeout occurred with the bus still in idle */ |
| |
| i2cinfo("Timeout with I2CxCON: %04x I2CxSTAT: %04x\n", con, stat); |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_isr_process |
| * |
| * Description: |
| * Common interrupt service routine |
| * |
| ****************************************************************************/ |
| |
| static int pic32mz_i2c_isr_process(struct pic32mz_i2c_priv_s *priv) |
| { |
| uint32_t status; |
| |
| status = pic32mz_i2c_getstatus(priv); |
| |
| /* Check for new trace setup */ |
| |
| pic32mz_i2c_tracenew(priv, status); |
| |
| switch (priv->process_state) |
| { |
| /* The process starts from this state after a call to i2c_transfer. |
| * It may return here in the case of a write/read transaction, |
| * to send the address with the READ bit set. |
| */ |
| |
| case PROCESS_STATE_SEND_ADDR: |
| |
| pic32mz_i2c_traceevent(priv, I2CEVENT_SENDADDR, priv->msgc); |
| |
| if (priv->msgc > 0 && priv->msgv != NULL) |
| { |
| priv->ptr = priv->msgv->buffer; |
| priv->dcnt = priv->msgv->length; |
| priv->flags = priv->msgv->flags; |
| |
| /* Send the address byte and set the next state to either |
| * read or transmit the data. |
| */ |
| |
| if (priv->flags & I2C_M_READ) |
| { |
| pic32mz_i2c_transmitbyte(priv, I2C_M_READ | priv->msgv->addr); |
| |
| priv->process_state = PROCESS_STATE_ENABLE_READ; |
| } |
| else |
| { |
| pic32mz_i2c_transmitbyte(priv, priv->msgv->addr); |
| |
| priv->process_state = PROCESS_STATE_SEND_DATA; |
| } |
| } |
| else |
| { |
| #ifndef CONFIG_I2C_POLLED |
| mips_clrpend_irq(priv->config->ev_irq); |
| #endif |
| } |
| break; |
| |
| /* This state is reached either after sending the address to the slave, |
| * or, in the case of multi-byte buffer, after sending a byte. |
| * We should first check that the previous transmission is not in |
| * progress, and that the slave had acknowledged it. |
| */ |
| |
| case PROCESS_STATE_SEND_DATA: |
| |
| pic32mz_i2c_traceevent(priv, I2CEVENT_SENDBYTE, priv->dcnt); |
| |
| /* No transmission is in progress. */ |
| |
| if ((status & I2C_STAT_TRSTAT) == 0) |
| { |
| /* ACK received from the slave. */ |
| |
| if ((status & I2C_STAT_ACKSTAT) == 0) |
| { |
| /* We need to keep one byte to send before we leave this state. |
| * This way we can trigger an interrupt and move to the next |
| * state. |
| */ |
| |
| if (priv->dcnt > 1) |
| { |
| pic32mz_i2c_transmitbyte(priv, *priv->ptr++); |
| |
| priv->dcnt--; |
| } |
| else |
| { |
| pic32mz_i2c_transmitbyte(priv, *priv->ptr++); |
| |
| priv->dcnt--; |
| |
| priv->process_state = PROCESS_STATE_FETCH_NEXT; |
| } |
| } |
| } |
| break; |
| |
| /* This state is reached after sending the address to the slave with |
| * the read bit set, or, in the case of multi-byte transfer, |
| * after reading the first byte. |
| * We should first check that the previous transmission is not in |
| * progress, and that the slave had acknowledged it. |
| */ |
| |
| case PROCESS_STATE_ENABLE_READ: |
| |
| pic32mz_i2c_traceevent(priv, I2CEVENT_RCVMODEEN, 0); |
| |
| /* No transmit is in progress. */ |
| |
| if ((status & I2C_STAT_TRSTAT) == 0) |
| { |
| /* ACK received from the slave. */ |
| |
| if ((status & I2C_STAT_ACKSTAT) == 0) |
| { |
| /* The master logic should be inactive before |
| * attempting to enable receive mode. |
| */ |
| |
| if (pic32mz_i2c_master_inactive(priv)) |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, |
| I2C_CON_RCEN); |
| |
| priv->process_state = PROCESS_STATE_READ_DATA; |
| } |
| } |
| } |
| break; |
| |
| /* This state reads a byte from the receive buffer. |
| * If there are more than one byte to read, |
| * it should go back to the previous state to enable |
| * the receive mode. |
| */ |
| |
| case PROCESS_STATE_READ_DATA: |
| |
| pic32mz_i2c_traceevent(priv, I2CEVENT_RCVBYTE, priv->dcnt); |
| |
| /* Is data available in the receiver buffer? */ |
| |
| if ((status & I2C_STAT_RBF) != 0) |
| { |
| /* Read and send an ACK */ |
| |
| if (priv->dcnt > 1) |
| { |
| #ifdef CONFIG_I2C_POLLED |
| irqstate_t flags = enter_critical_section(); |
| #endif |
| |
| *priv->ptr++ = pic32mz_i2c_receivebyte(priv); |
| |
| priv->dcnt--; |
| |
| /* The master logic should be inactive before |
| * attempting to issue an ACK. |
| */ |
| |
| if (pic32mz_i2c_master_inactive(priv)) |
| { |
| pic32mz_i2c_send_ack(priv, true); |
| } |
| |
| #ifdef CONFIG_I2C_POLLED |
| leave_critical_section(flags); |
| #endif |
| /* Go back and re-enable read mode to handle the rest of |
| * the data. |
| * It is cleared by the hardware at the end of the eighth bit. |
| */ |
| |
| priv->process_state = PROCESS_STATE_ENABLE_READ; |
| } |
| |
| /* Last byte, read and send a NACK */ |
| |
| else |
| { |
| #ifdef CONFIG_I2C_POLLED |
| irqstate_t flags = enter_critical_section(); |
| #endif |
| *priv->ptr++ = pic32mz_i2c_receivebyte(priv); |
| |
| priv->dcnt--; |
| |
| /* The master logic should be inactive before |
| * attempting to issue a NACK. |
| */ |
| |
| if (pic32mz_i2c_master_inactive(priv)) |
| { |
| pic32mz_i2c_send_ack(priv, false); |
| } |
| |
| #ifdef CONFIG_I2C_POLLED |
| leave_critical_section(flags); |
| #endif |
| priv->process_state = PROCESS_STATE_FETCH_NEXT; |
| } |
| } |
| |
| break; |
| |
| /* In this state we fetch the next mssage. |
| * Increment to next pointer and decrement message count. |
| * If we have an other set of data we will: |
| * - Issue a repeated start (I2C_M_NOSTOP flag set). |
| * - Continue with no start (I2C_M_NOSTART flag set). |
| * - Issue a start (No flag set). |
| * If no more data to send, issue a stop. |
| */ |
| |
| case PROCESS_STATE_FETCH_NEXT: |
| |
| priv->msgv++; |
| priv->msgc--; |
| |
| if (priv->msgc > 0 && priv->msgv != NULL) |
| { |
| /* If the previous message had the I2C_M_NOSTOP flag set, |
| * this implies that we should issue a repeated start. |
| * (Note: priv->flags still has the previous flags.) |
| */ |
| |
| if (priv->flags & I2C_M_NOSTOP) |
| { |
| pic32mz_i2c_traceevent(priv, I2CEVENT_STARTRESTART, 0); |
| |
| /* The bus should be in idle before issuing a repeated start. */ |
| |
| if ((pic32mz_i2c_master_inactive(priv)) && |
| (status & I2C_STAT_TRSTAT) == 0) |
| { |
| pic32mz_i2c_send_repeatedstart(priv); |
| priv->process_state = PROCESS_STATE_SEND_ADDR; |
| } |
| } |
| |
| /* If the new message has the I2C_M_NOSTART flag set, |
| * this means that it's a continuation of the same transfer. |
| * We can't just move back to SEND_DATA as we need an interrupt. |
| * So one byte must be sent from here first. |
| */ |
| |
| else if (priv->msgv->flags & I2C_M_NOSTART) |
| { |
| priv->ptr = priv->msgv->buffer; |
| priv->dcnt = priv->msgv->length; |
| priv->flags = priv->msgv->flags; |
| |
| pic32mz_i2c_traceevent(priv, I2CEVENT_NOSTART, priv->dcnt); |
| |
| if ((status & I2C_STAT_TRSTAT) == 0) |
| { |
| if ((status & I2C_STAT_ACKSTAT) == 0) |
| { |
| /* We have more than one byte. |
| * Send the first one, this will trigger an interrupt |
| * the rest will get sent later. |
| */ |
| |
| if (priv->dcnt > 1) |
| { |
| pic32mz_i2c_transmitbyte(priv, *priv->ptr++); |
| |
| priv->dcnt--; |
| |
| priv->process_state = PROCESS_STATE_SEND_DATA; |
| } |
| |
| /* Send the only byte we have and stay in this state |
| * to fetch the next message. |
| */ |
| |
| else |
| { |
| pic32mz_i2c_transmitbyte(priv, *priv->ptr++); |
| |
| priv->dcnt--; |
| } |
| } |
| } |
| } |
| |
| /* If neither the I2C_M_NOSTOP nor the I2C_M_NOSTART is set, |
| * just issue a start and let the isr process the data. |
| */ |
| |
| else |
| { |
| /* The bus should be in idle before issuing a start. */ |
| |
| if ((pic32mz_i2c_master_inactive(priv)) && |
| (status & I2C_STAT_TRSTAT) == 0) |
| { |
| pic32mz_i2c_send_start(priv); |
| |
| priv->process_state = PROCESS_STATE_SEND_ADDR; |
| } |
| } |
| } |
| else |
| { |
| /* The stop should be initiated here, |
| * as there is no other way to trigger an interrupt. |
| */ |
| |
| pic32mz_i2c_traceevent(priv, I2CEVENT_STOP, 0); |
| |
| /* The master logic should be inactive before |
| * attempting to issue a STOP. |
| */ |
| |
| if (pic32mz_i2c_master_inactive(priv)) |
| { |
| pic32mz_i2c_send_stop(priv); |
| |
| priv->process_state = PROCESS_STATE_TRANSFERT_DONE; |
| } |
| } |
| break; |
| |
| /* Arriving here, the transfer is complete. |
| * Wake up any thread that has been waiting for this event. |
| */ |
| |
| case PROCESS_STATE_TRANSFERT_DONE: |
| |
| pic32mz_i2c_traceevent(priv, I2CEVENT_WAKEUP, 0); |
| |
| if (priv->msgv) |
| { |
| /* Is there a thread waiting for this event (there should be) */ |
| |
| if (priv->intstate == INTSTATE_WAITING) |
| { |
| #ifndef CONFIG_I2C_POLLED |
| nxsem_post(&priv->sem_isr); |
| #endif |
| priv->intstate = INTSTATE_DONE; |
| } |
| |
| /* Mark that we have stopped with this transaction. */ |
| |
| priv->msgv = NULL; |
| } |
| |
| break; |
| |
| default: |
| |
| /* Nothing goes here! */ |
| |
| break; |
| } |
| |
| /* Clear the master interrupt flag. */ |
| |
| #ifndef CONFIG_I2C_POLLED |
| if (mips_pending_irq(priv->config->ev_irq)) |
| { |
| mips_clrpend_irq(priv->config->ev_irq); |
| } |
| #endif |
| |
| /* If an error interrupt has occurred. */ |
| |
| #ifndef CONFIG_I2C_POLLED |
| if (mips_pending_irq(priv->config->er_irq)) |
| { |
| pic32mz_i2c_traceevent(priv, I2CEVENT_ERROR, 0); |
| |
| mips_clrpend_irq(priv->config->er_irq); |
| } |
| #endif |
| |
| priv->status = status; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_isr |
| * |
| * Description: |
| * Common I2C interrupt service routine. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_I2C_POLLED |
| static int pic32mz_i2c_isr(int irq, void *context, void *arg) |
| { |
| struct pic32mz_i2c_priv_s *priv = (struct pic32mz_i2c_priv_s *)arg; |
| |
| DEBUGASSERT(priv != NULL); |
| return pic32mz_i2c_isr_process(priv); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_setbaudrate |
| * |
| * Description: |
| * Calculates the value of the baudrate. |
| * |
| ****************************************************************************/ |
| |
| static inline int |
| pic32mz_i2c_setbaudrate(struct pic32mz_i2c_priv_s *priv, |
| uint32_t frequency) |
| { |
| uint32_t baudrate; |
| |
| if (frequency != priv->frequency) |
| { |
| /* BOARD_PBCLK and frequency are both given in Hz. */ |
| |
| baudrate = (uint32_t)(((BOARD_PBCLK2 / (2 * frequency)) - |
| (BOARD_PBCLK2 / 10000000) - 2)); |
| |
| /* Values of 0x0 and 0x1 are prohibited. */ |
| |
| if (baudrate == 0x0 || baudrate == 0x1) |
| { |
| return ERROR; |
| } |
| else |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_BRG_OFFSET, baudrate); |
| priv->frequency = frequency; |
| |
| /* Enable Slew Rate Control when operating on High Speed mode. */ |
| |
| if (frequency == 400000) |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONCLR_OFFSET, |
| I2C_CON_DISSLW); |
| } |
| else |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, |
| I2C_CON_DISSLW); |
| } |
| } |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_send_start |
| * |
| * Description: |
| * Initiate a start condition. |
| * |
| ****************************************************************************/ |
| |
| static inline void |
| pic32mz_i2c_send_start(struct pic32mz_i2c_priv_s *priv) |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, I2C_CON_SEN); |
| |
| /* To avoid bus collision during polling. */ |
| |
| #ifdef CONFIG_I2C_POLLED |
| while ((pic32mz_i2c_getreg(priv, PIC32MZ_I2C_CON_OFFSET) & |
| I2C_CON_SEN) != 0); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_send_stop |
| * |
| * Description: |
| * Initiate a stop condition. |
| * |
| ****************************************************************************/ |
| |
| static inline void pic32mz_i2c_send_stop(struct pic32mz_i2c_priv_s *priv) |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, I2C_CON_PEN); |
| |
| /* To avoid bus collision during polling. */ |
| |
| #ifdef CONFIG_I2C_POLLED |
| while ((pic32mz_i2c_getreg(priv, PIC32MZ_I2C_CON_OFFSET) & |
| I2C_CON_PEN) != 0); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_send_repeatedstart |
| * |
| * Description: |
| * Initiate a repeated start condition. |
| * |
| ****************************************************************************/ |
| |
| static inline void |
| pic32mz_i2c_send_repeatedstart(struct pic32mz_i2c_priv_s *priv) |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, I2C_CON_RSEN); |
| |
| /* To avoid bus collision during polling. */ |
| |
| #ifdef CONFIG_I2C_POLLED |
| while ((pic32mz_i2c_getreg(priv, PIC32MZ_I2C_CON_OFFSET) & |
| I2C_CON_RSEN) != 0); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_send_ack |
| * |
| * Description: |
| * Issue an ACK or a NACK. |
| * |
| ****************************************************************************/ |
| |
| static inline void pic32mz_i2c_send_ack(struct pic32mz_i2c_priv_s *priv, |
| bool ack) |
| { |
| if (ack) |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONCLR_OFFSET, I2C_CON_ACKDT); |
| } |
| else |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, I2C_CON_ACKDT); |
| } |
| |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, I2C_CON_ACKEN); |
| |
| /* To avoid bus collision during polling. */ |
| |
| #ifdef CONFIG_I2C_POLLED |
| while ((pic32mz_i2c_getreg(priv, PIC32MZ_I2C_CON_OFFSET) & |
| I2C_CON_ACKEN) != 0); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_transmitbyte |
| * |
| * Description: |
| * Transmit a byte. |
| * |
| ****************************************************************************/ |
| |
| static inline void pic32mz_i2c_transmitbyte(struct pic32mz_i2c_priv_s *priv, |
| uint8_t data) |
| { |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_TRN_OFFSET, data); |
| |
| /* To avoid bus collision during polling. */ |
| |
| #ifdef CONFIG_I2C_POLLED |
| while ((pic32mz_i2c_getreg(priv, PIC32MZ_I2C_STAT_OFFSET) & |
| I2C_STAT_TRSTAT) != 0); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_receivebyte |
| * |
| * Description: |
| * Receive a byte. |
| * |
| ****************************************************************************/ |
| |
| static inline uint32_t |
| pic32mz_i2c_receivebyte(struct pic32mz_i2c_priv_s *priv) |
| { |
| uint32_t val; |
| |
| /* To avoid bus collision during polling. */ |
| |
| #ifdef CONFIG_I2C_POLLED |
| while ((pic32mz_i2c_getreg(priv, PIC32MZ_I2C_STAT_OFFSET) & |
| I2C_CON_RCEN) != 0); |
| #endif |
| |
| val = pic32mz_i2c_getreg(priv, PIC32MZ_I2C_RCV_OFFSET); |
| |
| return val; |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_master_inactive |
| * |
| * Description: |
| * Check if the bus is inactive. |
| * No start, stop, ACK is in progress. |
| * |
| ****************************************************************************/ |
| |
| static inline bool |
| pic32mz_i2c_master_inactive(struct pic32mz_i2c_priv_s *priv) |
| { |
| uint32_t con; |
| |
| con = pic32mz_i2c_getreg(priv, PIC32MZ_I2C_CON_OFFSET); |
| |
| return ((con & I2C_CON_IDLEMASK) ? false:true); |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_getstatus |
| * |
| * Description: |
| * Get the STAT register. |
| * |
| ****************************************************************************/ |
| |
| static inline uint32_t |
| pic32mz_i2c_getstatus(struct pic32mz_i2c_priv_s *priv) |
| { |
| return pic32mz_i2c_getreg(priv, PIC32MZ_I2C_STAT_OFFSET); |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_init |
| * |
| * Description: |
| * Setup the I2C hardware, ready for operation with defaults |
| * |
| ****************************************************************************/ |
| |
| static int pic32mz_i2c_init(struct pic32mz_i2c_priv_s *priv) |
| { |
| /* Force a frequency update */ |
| |
| priv->frequency = 0; |
| pic32mz_i2c_setbaudrate(priv, 100000); |
| |
| /* Attach ISRs */ |
| |
| #ifndef CONFIG_I2C_POLLED |
| irq_attach(priv->config->ev_irq, pic32mz_i2c_isr, priv); |
| irq_attach(priv->config->er_irq, pic32mz_i2c_isr, priv); |
| #endif |
| |
| /* Enable the I2C hardware. |
| * The I2C hardware, when enabled, takes control over the pins. |
| * The module overrides the port state and direction. |
| * No need to configure the pins here. |
| */ |
| |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONSET_OFFSET, I2C_CON_ON); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_deinit |
| * |
| * Description: |
| * Shutdown the I2C hardware |
| * |
| ****************************************************************************/ |
| |
| static int pic32mz_i2c_deinit(struct pic32mz_i2c_priv_s *priv) |
| { |
| /* Disable I2C */ |
| |
| pic32mz_i2c_putreg(priv, PIC32MZ_I2C_CONCLR_OFFSET, I2C_CON_ON); |
| |
| /* Disable and detach ISRs */ |
| |
| #ifndef CONFIG_I2C_POLLED |
| up_disable_irq(priv->config->ev_irq); |
| up_disable_irq(priv->config->er_irq); |
| irq_detach(priv->config->ev_irq); |
| irq_detach(priv->config->er_irq); |
| #endif |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Device Driver Operations |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_transfer |
| * |
| * Description: |
| * Generic I2C transfer function |
| * |
| ****************************************************************************/ |
| |
| static int pic32mz_i2c_transfer(struct i2c_master_s *dev, |
| struct i2c_msg_s *msgs, int count) |
| { |
| struct pic32mz_i2c_priv_s *priv = (struct pic32mz_i2c_priv_s *)dev; |
| uint32_t status = 0; |
| int ret; |
| |
| /* Acquire the mutex. */ |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Wait for the bus to be in an idle state. */ |
| |
| pic32mz_i2c_sem_waitidle(priv); |
| |
| /* Clear any pending error interrupts. */ |
| |
| #ifndef CONFIG_I2C_POLLED |
| mips_clrpend_irq(priv->config->er_irq); |
| #endif |
| |
| /* 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; |
| |
| /* Reset I2C trace logic */ |
| |
| pic32mz_i2c_tracereset(priv); |
| |
| /* Set the baudrate. */ |
| |
| pic32mz_i2c_setbaudrate(priv, priv->frequency); |
| |
| #ifndef CONFIG_I2C_POLLED |
| |
| /* Enable interrupts here so when we send the start condition |
| * below the ISR will fire if the data was sent. |
| */ |
| |
| up_enable_irq(priv->config->ev_irq); |
| up_enable_irq(priv->config->er_irq); |
| #endif |
| |
| priv->status = 0; |
| |
| pic32mz_i2c_send_start(priv); |
| |
| /* Indicate to the process where to start. */ |
| |
| priv->process_state = PROCESS_STATE_SEND_ADDR; |
| |
| if (pic32mz_i2c_sem_waitdone(priv) < 0) |
| { |
| status = pic32mz_i2c_getstatus(priv); |
| ret = -ETIMEDOUT; |
| |
| i2cerr("ERROR: Timed out: CON: 0x%04x status: 0x%04x\n", |
| pic32mz_i2c_getreg(priv, PIC32MZ_I2C_CON_OFFSET), status); |
| } |
| else |
| { |
| status = pic32mz_i2c_getstatus(priv); |
| } |
| |
| /* Check for errors. */ |
| |
| if ((status & I2C_STAT_BCL)) |
| { |
| ret = -EIO; |
| } |
| |
| else if ((status & I2C_STAT_IWCOL) != 0) |
| { |
| ret = -EIO; |
| } |
| |
| else if ((status & I2C_STAT_I2COV) != 0) |
| { |
| ret = -EIO; |
| } |
| |
| /* Dump the trace result */ |
| |
| pic32mz_i2c_tracedump(priv); |
| |
| /* Ensure ISR happening after we finish can't overwrite any user data */ |
| |
| priv->dcnt = 0; |
| priv->ptr = NULL; |
| |
| nxmutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2c_reset |
| * |
| * Description: |
| * Perform an I2C bus reset in an attempt to break loose stuck I2C devices. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_I2C_RESET |
| static int pic32mz_i2c_reset(struct i2c_master_s *dev) |
| { |
| struct pic32mz_i2c_priv_s *priv = (struct pic32mz_i2c_priv_s *)dev; |
| unsigned int clock_count; |
| unsigned int stretch_count; |
| uint32_t frequency; |
| int ret = ERROR; |
| |
| DEBUGASSERT(dev); |
| |
| /* Our caller must own a ref */ |
| |
| DEBUGASSERT(priv->refs > 0); |
| |
| /* Lock out other clients */ |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Save the current frequency */ |
| |
| frequency = priv->frequency; |
| |
| /* De-init the port */ |
| |
| pic32mz_i2c_deinit(priv); |
| |
| /* Use GPIO configuration to un-wedge the bus */ |
| |
| pic32mz_configgpio(priv->scl); |
| pic32mz_configgpio(priv->sda); |
| |
| /* Let SDA go high */ |
| |
| pic32mz_gpiowrite(priv->sda, 1); |
| |
| /* Clock the bus until any slaves currently driving it let it go. */ |
| |
| clock_count = 0; |
| while (!pic32mz_gpioread(priv->sda)) |
| { |
| /* Give up if we have tried too hard */ |
| |
| if (clock_count++ > 10) |
| { |
| goto out; |
| } |
| |
| /* Sniff to make sure that clock stretching has finished. |
| * |
| * If the bus never relaxes, the reset has failed. |
| */ |
| |
| stretch_count = 0; |
| while (!pic32mz_gpioread(priv->scl)) |
| { |
| /* Give up if we have tried too hard */ |
| |
| if (stretch_count++ > 10) |
| { |
| goto out; |
| } |
| |
| up_udelay(10); |
| } |
| |
| /* Drive SCL low */ |
| |
| pic32mz_gpiowrite(priv->scl, 0); |
| up_udelay(10); |
| |
| /* Drive SCL high again */ |
| |
| pic32mz_gpiowrite(priv->scl, 1); |
| up_udelay(10); |
| } |
| |
| /* Generate a start followed by a stop to reset slave |
| * state machines. |
| */ |
| |
| pic32mz_gpiowrite(priv->sda, 0); |
| up_udelay(10); |
| pic32mz_gpiowrite(priv->scl, 0); |
| up_udelay(10); |
| pic32mz_gpiowrite(priv->scl, 1); |
| up_udelay(10); |
| pic32mz_gpiowrite(priv->sda, 1); |
| up_udelay(10); |
| |
| /* Revert the GPIO configuration. */ |
| |
| pic32mz_unconfiggpio(priv->sda); |
| pic32mz_unconfiggpio(priv->scl); |
| |
| /* Re-init the port */ |
| |
| pic32mz_i2c_init(priv); |
| |
| /* Restore the frequency */ |
| |
| pic32mz_i2c_setclock(priv, frequency); |
| ret = OK; |
| |
| out: |
| |
| /* Release the port for reuse by other clients */ |
| |
| nxmutex_unlock(&priv->lock); |
| } |
| #endif /* CONFIG_I2C_RESET */ |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2cbus_initialize |
| * |
| * Description: |
| * Initialize one I2C bus |
| * |
| ****************************************************************************/ |
| |
| struct i2c_master_s *pic32mz_i2cbus_initialize(int port) |
| { |
| struct pic32mz_i2c_priv_s *priv = NULL; |
| |
| /* Get I2C private structure */ |
| |
| switch (port) |
| { |
| #ifdef CONFIG_PIC32MZ_I2C1 |
| case 1: |
| priv = (struct pic32mz_i2c_priv_s *)&pic32mz_i2c1_priv; |
| break; |
| #endif |
| |
| #ifdef CONFIG_PIC32MZ_I2C2 |
| case 2: |
| priv = (struct pic32mz_i2c_priv_s *)&pic32mz_i2c2_priv; |
| break; |
| #endif |
| |
| #ifdef CONFIG_PIC32MZ_I2C3 |
| case 3: |
| priv = (struct pic32mz_i2c_priv_s *)&pic32mz_i2c3_priv; |
| break; |
| #endif |
| #ifdef CONFIG_PIC32MZ_I2C4 |
| case 4: |
| priv = (struct pic32mz_i2c_priv_s *)&pic32mz_i2c4_priv; |
| break; |
| #endif |
| |
| #ifdef CONFIG_PIC32MZ_I2C5 |
| case 5: |
| priv = (struct pic32mz_i2c_priv_s *)&pic32mz_i2c5_priv; |
| break; |
| #endif |
| |
| default: |
| return NULL; |
| } |
| |
| /* Initialize private data for the first time, increment reference count, |
| * power-up hardware and configure GPIOs. |
| */ |
| |
| nxmutex_lock(&priv->lock); |
| if (priv->refs++ == 0) |
| { |
| pic32mz_i2c_init(priv); |
| } |
| |
| nxmutex_unlock(&priv->lock); |
| return (struct i2c_master_s *)priv; |
| } |
| |
| /**************************************************************************** |
| * Name: pic32mz_i2cbus_uninitialize |
| * |
| * Description: |
| * Uninitialize an I2C bus |
| * |
| ****************************************************************************/ |
| |
| int pic32mz_i2cbus_uninitialize(struct i2c_master_s *dev) |
| { |
| struct pic32mz_i2c_priv_s *priv = (struct pic32mz_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) |
| { |
| nxmutex_unlock(&priv->lock); |
| return OK; |
| } |
| |
| /* Disable I2C hardware */ |
| |
| pic32mz_i2c_deinit(priv); |
| nxmutex_unlock(&priv->lock); |
| |
| return OK; |
| } |
| |
| #endif /* CONFIG_PIC32MZ_I2C */ |