/**************************************************************************** * arch/arm/src/sama5/sam_pio.c * General Purpose Input/Output (PIO) logic for the SAMA5 * * Copyright (C) 2013-2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include "up_internal.h" #include "up_arch.h" #include "chip/sam_pio.h" #include "chip.h" #include "sam_periphclks.h" #include "sam_pio.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Macros to convert a pin to a vanilla input */ #define PIO_INPUT_BITS (PIO_INPUT | PIO_CFG_DEFAULT) #define MK_INPUT(p) (((p) & (PIO_PORT_MASK | PIO_PIN_MASK)) | PIO_INPUT_BITS) /**************************************************************************** * Private Types ****************************************************************************/ /**************************************************************************** * Public Data ****************************************************************************/ /* SAM_PION_VBASE will only be defined if the PIO register blocks are * contiguous. If not defined, then we need to do a table lookup. */ #ifndef SAM_PION_VBASE const uintptr_t g_piobase[SAM_NPIO] = { SAM_PIOA_VBASE, SAM_PIOB_VBASE, SAM_PIOC_VBASE, SAM_PIOD_VBASE, SAM_PIOE_VBASE }; #endif /**************************************************************************** * Private Data ****************************************************************************/ /* Maps a port number to the standard port character */ #ifdef CONFIG_DEBUG_GPIO static const char g_portchar[SAM_NPIO] = { 'A', 'B', 'C', 'D', 'E' }; #endif /* Map a PIO number to the PIO peripheral identifier (PID) */ static const uint8_t g_piopid[SAM_NPIO] = { SAM_PID_PIOA, SAM_PID_PIOB, SAM_PID_PIOC, SAM_PID_PIOD, SAM_PID_PIOE }; /* Used to determine if a PIO port is configured to support interrupts */ static const bool g_piointerrupt[SAM_NPIO] = { #ifdef CONFIG_SAMA5_PIOA_IRQ true, #else false, #endif #ifdef CONFIG_SAMA5_PIOB_IRQ true, #else false, #endif #ifdef CONFIG_SAMA5_PIOC_IRQ true, #else false, #endif #ifdef CONFIG_SAMA5_PIOD_IRQ true, #else false, #endif #ifdef CONFIG_SAMA5_PIOE_IRQ true #else false #endif }; /* This is an array of ports that PIO enable forced on */ static uint32_t g_forced[SAM_NPIO]; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /**************************************************************************** * Name: sam_piobase * * Description: * Return the base address of the PIO register set * ****************************************************************************/ static inline uintptr_t sam_piobase(pio_pinset_t cfgset) { int port = (cfgset & PIO_PORT_MASK) >> PIO_PORT_SHIFT; if (port < SAM_NPIO) { return sam_pion_vbase(port); } else { return 0; } } /**************************************************************************** * Name: sam_piopin * * Description: * Return a bitmask corresponding to the bit position in a PIO register * ****************************************************************************/ static inline uint32_t sam_piopin(pio_pinset_t cfgset) { return 1 << ((cfgset & PIO_PIN_MASK) >> PIO_PIN_SHIFT); } /**************************************************************************** * Name: sam_pio_enableclk * * Description: * Enable clocking on the selected PIO * ****************************************************************************/ static void sam_pio_enableclk(pio_pinset_t cfgset) { int port = (cfgset & PIO_PORT_MASK) >> PIO_PORT_SHIFT; int pid; if (port < SAM_NPIO) { /* Get the peripheral ID associated with the PIO port and enable * clocking to the PIO block. */ pid = g_piopid[port]; if (pid < 32) { sam_enableperiph0(pid); } else { sam_enableperiph1(pid); } } } /**************************************************************************** * Name: sam_pio_disableclk * * Description: * Disable clocking on the selected PIO if we can. We can that if: * * 1) No pins are configured as PIO inputs (peripheral inputs don't need * clocking, and * 2) Glitch and debounce filtering are not enabled. Currently, this can * only happen if the the pin is a PIO input, but we may need to * implement glitch filtering on peripheral inputs as well in the * future??? * 3) The port is not configured for PIO interrupts. At present, the logic * always keeps clocking on to ports that are configured for interrupts, * but that could be dynamically controlled as well be keeping track * of which PIOs have interrupts enabled. * * My! Wouldn't is be much easier to just keep all of the PIO clocks * enabled? Is there a power management downside? * ****************************************************************************/ static void sam_pio_disableclk(pio_pinset_t cfgset) { int port = (cfgset & PIO_PORT_MASK) >> PIO_PORT_SHIFT; uintptr_t base; int pid; /* Leave clocking enabled for configured interrupt ports or for ports that * have forced enabling of PIO clocking. */ if (port < SAM_NPIO && !g_piointerrupt[port] && g_forced[port] == 0) { /* Get the base address of the PIO port */ base = sam_pion_vbase(port); /* Are any pins configured as PIO inputs? * * PSR - A bit set to "1" means that the corresponding pin is a PIO * OSR - A bit set to "1" means that the corresponding pin is an output */ if ((getreg32(base + SAM_PIO_PSR_OFFSET) & ~getreg32(base + SAM_PIO_PSR_OFFSET)) == 0) { /* Any remaining configured pins are either not PIOs or all not * PIO inputs. Disable clocking to this PIO block. * * Get the peripheral ID associated with the PIO port and disable * clocking to the PIO block. */ pid = g_piopid[port]; if (pid < 32) { sam_disableperiph0(pid); } else { sam_disableperiph1(pid); } } } } /**************************************************************************** * Name: sam_configinput * * Description: * Configure a PIO input pin based on bit-encoded description of the pin. * ****************************************************************************/ static inline int sam_configinput(uintptr_t base, uint32_t pin, pio_pinset_t cfgset) { #if defined(PIO_HAVE_SCHMITT) || defined(PIO_HAVE_DRIVE) uint32_t regval; #endif #if defined(PIO_HAVE_DRIVE) uint32_t offset; uint32_t mask; uint32_t drive; int shift; #endif /* Disable interrupts on the pin */ putreg32(pin, base + SAM_PIO_IDR_OFFSET); /* Enable/disable the pull-up as requested */ if ((cfgset & PIO_CFG_PULLUP) != 0) { putreg32(pin, base + SAM_PIO_PUER_OFFSET); } else { putreg32(pin, base + SAM_PIO_PUDR_OFFSET); } #ifdef PIO_HAVE_PULLDOWN /* Enable/disable the pull-down as requested */ if ((cfgset & PIO_CFG_PULLDOWN) != 0) { putreg32(pin, base + SAM_PIO_PPDER_OFFSET); } else { putreg32(pin, base + SAM_PIO_PPDDR_OFFSET); } #endif /* Check if filtering should be enabled */ if ((cfgset & PIO_CFG_DEGLITCH) != 0) { putreg32(pin, base + SAM_PIO_IFER_OFFSET); } else { putreg32(pin, base + SAM_PIO_IFDR_OFFSET); } #ifdef PIO_HAVE_SCHMITT /* Enable/disable the Schmitt trigger: Zero enables. Schmitt triggered * inputs are enabled by default. */ regval = getreg32(base + SAM_PIO_SCHMITT_OFFSET); if ((cfgset & PIO_CFG_SCHMITT) != 0) { regval &= ~pin; } else { regval |= pin; } putreg32(regval, base + SAM_PIO_SCHMITT_OFFSET); #endif #ifdef PIO_HAVE_DRIVE /* Configure drive strength */ drive = (cfgset & PIO_DRIVE_MASK) >> PIO_DRIVE_SHIFT; if (pin < 32) { offset = SAM_PIO_DRIVER1_OFFSET; mask = PIO_DRIVER1_LINE_MASK(pin); shift = PIO_DRIVER1_LINE_SHIFT(pin); } else { offset = SAM_PIO_DRIVER2_OFFSET; mask = PIO_DRIVER2_LINE_MASK(pin); shift = PIO_DRIVER2_LINE_SHIFT(pin); } regval = getreg32(base + offset); regval &= ~mask; regval |= drive << shift; putreg32(regval, base + offset); #endif /* Clear some output only bits. Mostly this just simplifies debug. */ putreg32(pin, base + SAM_PIO_MDDR_OFFSET); putreg32(pin, base + SAM_PIO_CODR_OFFSET); /* Configure the pin as an input and enable the PIO function */ putreg32(pin, base + SAM_PIO_ODR_OFFSET); putreg32(pin, base + SAM_PIO_PER_OFFSET); /* To-Do: If DEGLITCH is selected, need to configure DIFSR, SCIFSR, and * IFDGSR registers. This would probably best be done with * another, new API... perhaps sam_configfilter() */ /* "Reading the I/O line levels requires the clock of the PIO Controller * to be enabled, otherwise PIO_PDSR reads the levels present on the I/O * line at the time the clock was disabled." */ sam_pio_enableclk(cfgset); return OK; } /**************************************************************************** * Name: sam_configoutput * * Description: * Configure a PIO output pin based on bit-encoded description of the pin. * ****************************************************************************/ static inline int sam_configoutput(uintptr_t base, uint32_t pin, pio_pinset_t cfgset) { /* Disable interrupts on the pin */ putreg32(pin, base + SAM_PIO_IDR_OFFSET); /* Enable/disable the pull-up as requested */ if ((cfgset & PIO_CFG_PULLUP) != 0) { putreg32(pin, base + SAM_PIO_PUER_OFFSET); } else { putreg32(pin, base + SAM_PIO_PUDR_OFFSET); } #ifdef PIO_HAVE_PULLDOWN /* Enable/disable the pull-down as requested */ if ((cfgset & PIO_CFG_PULLDOWN) != 0) { putreg32(pin, base + SAM_PIO_PPDER_OFFSET); } else { putreg32(pin, base + SAM_PIO_PPDDR_OFFSET); } #endif /* Disable glitch filtering */ putreg32(pin, base + SAM_PIO_IFDR_OFFSET); /* Enable the open drain driver if requested */ if ((cfgset & PIO_CFG_OPENDRAIN) != 0) { putreg32(pin, base + SAM_PIO_MDER_OFFSET); } else { putreg32(pin, base + SAM_PIO_MDDR_OFFSET); } /* Set default value. This is to be done before the pin is configured as * an output in order to avoid any glitches at the time of the * configuration. */ if ((cfgset & PIO_OUTPUT_SET) != 0) { putreg32(pin, base + SAM_PIO_SODR_OFFSET); } else { putreg32(pin, base + SAM_PIO_CODR_OFFSET); } /* Configure the pin as an output and enable the PIO function */ putreg32(pin, base + SAM_PIO_OER_OFFSET); putreg32(pin, base + SAM_PIO_PER_OFFSET); /* Clocking to the PIO block may no longer be necessary. */ sam_pio_disableclk(cfgset); return OK; } /**************************************************************************** * Name: sam_configperiph * * Description: * Configure a PIO pin driven by a peripheral A or B signal based on * bit-encoded description of the pin. * ****************************************************************************/ static inline int sam_configperiph(uintptr_t base, uint32_t pin, pio_pinset_t cfgset) { uint32_t regval; /* Disable interrupts on the pin */ putreg32(pin, base + SAM_PIO_IDR_OFFSET); /* Enable/disable the pull-up as requested */ if ((cfgset & PIO_CFG_PULLUP) != 0) { putreg32(pin, base + SAM_PIO_PUER_OFFSET); } else { putreg32(pin, base + SAM_PIO_PUDR_OFFSET); } #ifdef PIO_HAVE_PULLDOWN /* Enable/disable the pull-down as requested */ if ((cfgset & PIO_CFG_PULLDOWN) != 0) { putreg32(pin, base + SAM_PIO_PPDER_OFFSET); } else { putreg32(pin, base + SAM_PIO_PPDDR_OFFSET); } #endif /* Disable glitch filtering */ putreg32(pin, base + SAM_PIO_IFDR_OFFSET); #ifdef PIO_HAVE_PERIPHCD /* Configure pin, depending upon the peripheral A, B, C or D * * PERIPHA: ABCDSR1[n] = 0 ABCDSR2[n] = 0 * PERIPHB: ABCDSR1[n] = 1 ABCDSR2[n] = 0 * PERIPHC: ABCDSR1[n] = 0 ABCDSR2[n] = 1 * PERIPHD: ABCDSR1[n] = 1 ABCDSR2[n] = 1 */ regval = getreg32(base + SAM_PIO_ABCDSR1_OFFSET); if ((cfgset & PIO_MODE_MASK) == PIO_PERIPHA || (cfgset & PIO_MODE_MASK) == PIO_PERIPHC) { regval &= ~pin; } else { regval |= pin; } putreg32(regval, base + SAM_PIO_ABCDSR1_OFFSET); regval = getreg32(base + SAM_PIO_ABCDSR2_OFFSET); if ((cfgset & PIO_MODE_MASK) == PIO_PERIPHA || (cfgset & PIO_MODE_MASK) == PIO_PERIPHB) { regval &= ~pin; } else { regval |= pin; } putreg32(regval, base + SAM_PIO_ABCDSR2_OFFSET); #else /* Configure pin, depending upon the peripheral A or B: * * PERIPHA: ABSR[n] = 0 * PERIPHB: ABSR[n] = 1 */ regval = getreg32(base + SAM_PIO_ABSR_OFFSET); if ((cfgset & PIO_MODE_MASK) == PIO_PERIPHA) { regval &= ~pin; } else { regval |= pin; } putreg32(regval, base + SAM_PIO_ABSR_OFFSET); #endif /* Disable PIO functionality */ putreg32(pin, base + SAM_PIO_PDR_OFFSET); /* Clocking to the PIO block may no longer be necessary. */ sam_pio_disableclk(cfgset); return OK; } /**************************************************************************** * Global Functions ****************************************************************************/ /**************************************************************************** * Name: sam_configpio * * Description: * Configure a PIO pin based on bit-encoded description of the pin. * ****************************************************************************/ int sam_configpio(pio_pinset_t cfgset) { uintptr_t base; uint32_t pin; irqstate_t flags; int ret; /* Sanity check */ base = sam_piobase(cfgset); if (base == 0) { return -EINVAL; } pin = sam_piopin(cfgset); /* Disable interrupts to prohibit re-entrance. */ flags = irqsave(); /* Enable writing to PIO registers. The following registers are protected: * * - PIO Enable/Disable Registers (PER/PDR) * - PIO Output Enable/Disable Registers (OER/ODR) * - PIO Interrupt Security Level Register (ISLR) * - PIO Input Filter Enable/Disable Registers (IFER/IFDR) * - PIO Multi-driver Enable/Disable Registers (MDER/MDDR) * - PIO Pull-Up Enable/Disable Registers (PUER/PUDR) * - PIO Peripheral ABCD Select Register 1/2 (ABCDSR1/2) * - PIO Output Write Enable/Disable Registers * - PIO Pad Pull-Down Enable/Disable Registers (PPER/PPDR) * * I suspect that the default state is the WPMR is unprotected, so these * operations could probably all be avoided. */ putreg32(PIO_WPMR_WPKEY, base + SAM_PIO_WPMR_OFFSET); /* Put the pin in an intial state -- a vanilla input pint */ (void)sam_configinput(base, pin, MK_INPUT(cfgset)); /* Then handle the real pin configuration according to pin type */ switch (cfgset & PIO_MODE_MASK) { case PIO_INPUT: ret = sam_configinput(base, pin, cfgset); break; case PIO_OUTPUT: ret = sam_configoutput(base, pin, cfgset); break; case PIO_PERIPHA: case PIO_PERIPHB: #ifdef PIO_HAVE_PERIPHCD case PIO_PERIPHC: case PIO_PERIPHD: #endif ret = sam_configperiph(base, pin, cfgset); break; default: ret = -EINVAL; break; } /* Disable writing to PIO registers */ putreg32(PIO_WPMR_WPEN | PIO_WPMR_WPKEY, base + SAM_PIO_WPMR_OFFSET); irqrestore(flags); return ret; } /**************************************************************************** * Name: sam_piowrite * * Description: * Write one or zero to the selected PIO pin * ****************************************************************************/ void sam_piowrite(pio_pinset_t pinset, bool value) { uintptr_t base = sam_piobase(pinset); uint32_t pin = sam_piopin(pinset); if (base != 0) { /* Set or clear the output as requested. NOTE: that there is no * check if the pin is actually configured as an output so this could, * potentially, do nothing. */ if (value) { putreg32(pin, base + SAM_PIO_SODR_OFFSET); } else { putreg32(pin, base + SAM_PIO_CODR_OFFSET); } } } /**************************************************************************** * Name: sam_pioread * * Description: * Read one or zero from the selected PIO pin * ****************************************************************************/ bool sam_pioread(pio_pinset_t pinset) { uintptr_t base = sam_piobase(pinset); uint32_t pin; uint32_t regval; if (base != 0) { pin = sam_piopin(pinset); /* For output PIOs, the ODSR register provides the output value to * drive the pin. The PDSR register, on the the other hand, provides * the current sensed value on a pin, whether the pin is configured * as an input, an output or as a peripheral. * * There is small delay between the setting in ODSR and PDSR but * otherwise the they should be the same unless something external * is driving the pin. * * Let's assume that PDSR is what the caller wants. */ regval = getreg32(base + SAM_PIO_PDSR_OFFSET); return (regval & pin) != 0; } return 0; } /************************************************************************************ * Name: sam_pio_forceclk * * Description: * Enable PIO clocking. This logic is overly conservative and does not enable PIO * clocking unless necessary (PIO input selected, glitch/filtering enable, or PIO * interrupts enabled). There are, however, certain conditions were we may want * for force the PIO clock to be enabled. An example is reading the input value * from an open drain output. * * The PIO automatic enable/disable logic is not smart enough enough to know about * these cases. For those cases, sam_pio_forceclk() is provided. * ************************************************************************************/ void sam_pio_forceclk(pio_pinset_t pinset, bool enable) { unsigned int port; uint32_t pin; irqstate_t flags; /* Extract the port number */ port = (pinset & PIO_PORT_MASK) >> PIO_PORT_SHIFT; pin = sam_piopin(pinset); /* The remainder of this operation must be atomic */ flags = irqsave(); /* Are we enabling or disabling clocking */ if (enable) { /* Indicate that clocking is forced and enable the clock */ g_forced[port] |= pin; sam_pio_enableclk(pinset); } else { /* Clocking is no longer forced for this pin */ g_forced[port] &= ~pin; sam_pio_disableclk(pinset); } irqrestore(flags); } /************************************************************************************ * Function: sam_dumppio * * Description: * Dump all PIO registers associated with the base address of the provided pinset. * ************************************************************************************/ #ifdef CONFIG_DEBUG_GPIO int sam_dumppio(uint32_t pinset, const char *msg) { irqstate_t flags; uintptr_t base; unsigned int port; /* Get the base address associated with the PIO port */ port = (pinset & PIO_PORT_MASK) >> PIO_PORT_SHIFT; base = sam_pion_vbase(port); /* The following requires exclusive access to the PIO registers */ flags = irqsave(); lldbg("PIO%c pinset: %08x base: %08x -- %s\n", g_portchar[port], pinset, base, msg); #ifdef SAM_PIO_ISLR_OFFSET lldbg(" PSR: %08x ISLR: %08x OSR: %08x IFSR: %08x\n", getreg32(base + SAM_PIO_PSR_OFFSET), getreg32(base + SAM_PIO_ISLR_OFFSET), getreg32(base + SAM_PIO_OSR_OFFSET), getreg32(base + SAM_PIO_IFSR_OFFSET)); #else lldbg(" PSR: %08x OSR: %08x IFSR: %08x\n", getreg32(base + SAM_PIO_PSR_OFFSET), getreg32(base + SAM_PIO_OSR_OFFSET), getreg32(base + SAM_PIO_IFSR_OFFSET)); #endif lldbg(" ODSR: %08x PDSR: %08x IMR: %08x ISR: %08x\n", getreg32(base + SAM_PIO_ODSR_OFFSET), getreg32(base + SAM_PIO_PDSR_OFFSET), getreg32(base + SAM_PIO_IMR_OFFSET), getreg32(base + SAM_PIO_ISR_OFFSET)); lldbg(" MDSR: %08x PUSR: %08x ABDCSR: %08x %08x\n", getreg32(base + SAM_PIO_MDSR_OFFSET), getreg32(base + SAM_PIO_PUSR_OFFSET), getreg32(base + SAM_PIO_ABCDSR1_OFFSET), getreg32(base + SAM_PIO_ABCDSR2_OFFSET)); lldbg(" IFSCSR: %08x SCDR: %08x PPDSR: %08x OWSR: %08x\n", getreg32(base + SAM_PIO_IFSCSR_OFFSET), getreg32(base + SAM_PIO_SCDR_OFFSET), getreg32(base + SAM_PIO_PPDSR_OFFSET), getreg32(base + SAM_PIO_OWSR_OFFSET)); #ifdef SAM_PIO_LOCKSR_OFFSET lldbg(" AIMMR: %08x ELSR: %08x FRLHSR: %08x LOCKSR: %08x\n", getreg32(base + SAM_PIO_AIMMR_OFFSET), getreg32(base + SAM_PIO_ELSR_OFFSET), getreg32(base + SAM_PIO_FRLHSR_OFFSET), getreg32(base + SAM_PIO_LOCKSR_OFFSET)); #else lldbg(" AIMMR: %08x ELSR: %08x FRLHSR: %08x\n", getreg32(base + SAM_PIO_AIMMR_OFFSET), getreg32(base + SAM_PIO_ELSR_OFFSET), getreg32(base + SAM_PIO_FRLHSR_OFFSET)); #endif lldbg("SCHMITT: %08x DRIVER: %08x %08x\n", getreg32(base + SAM_PIO_SCHMITT_OFFSET), getreg32(base + SAM_PIO_DRIVER1_OFFSET), getreg32(base + SAM_PIO_DRIVER2_OFFSET)); lldbg(" WPMR: %08x WPSR: %08x\n", getreg32(base + SAM_PIO_WPMR_OFFSET), getreg32(base + SAM_PIO_WPSR_OFFSET)); irqrestore(flags); return OK; } #endif