blob: ab4192bfdd4e4a8d34d657cb8070d938fcdc3943 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/sama5/sama5d2x_pio.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 <stdint.h>
#include <time.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <arch/board/board.h>
#include "arm_internal.h"
#include "hardware/_sama5d2x_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
****************************************************************************/
/* Lookup for non-secure PIOs */
const uintptr_t g_piobase[SAM_NPIO] =
{
SAM_PIO_IOGROUPA_VBASE
#if SAM_NPIO > 1
, SAM_PIO_IOGROUPB_VBASE
#endif
#if SAM_NPIO > 2
, SAM_PIO_IOGROUPC_VBASE
#endif
#if SAM_NPIO > 3
, SAM_PIO_IOGROUPD_VBASE
#endif
};
/* Lookup for non-secure PIOs */
const uintptr_t g_spiobase[SAM_NPIO] =
{
SAM_SPIO_IOGROUPA_VBASE
#if SAM_NPIO > 1
, SAM_SPIO_IOGROUPB_VBASE
#endif
#if SAM_NPIO > 2
, SAM_SPIO_IOGROUPC_VBASE
#endif
#if SAM_NPIO > 3
, SAM_SPIO_IOGROUPD_VBASE
#endif
};
/****************************************************************************
* Private Data
****************************************************************************/
/* Maps a port number to the standard port character */
#if defined(CONFIG_DEBUG_GPIO_INFO) && SAM_NPIO > 0
static const char g_portchar[SAM_NPIO] =
{
'A'
#if SAM_NPIO > 1
, 'B'
#endif
#if SAM_NPIO > 2
, 'C'
#endif
#if SAM_NPIO > 3
, 'D'
#endif
};
#endif
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: sam_issecure
*
* Description:
* Return true if the configuration selects a secure port.
*
****************************************************************************/
static inline bool sam_issecure(pio_pinset_t cfgset)
{
return ((cfgset & PIO_INT_SECURE) != 0);
}
/****************************************************************************
* Name: sam_piobase
*
* Description:
* Return the base address of the PIO register set
*
****************************************************************************/
static uintptr_t sam_piobase(pio_pinset_t cfgset)
{
int port = (cfgset & PIO_PORT_MASK) >> PIO_PORT_SHIFT;
/* Verify that the port number is within range */
if (port < SAM_NPIO)
{
/* Is this a secure or an un-secured PIO? */
if (sam_issecure(cfgset))
{
/* Return the base address of the secure PIO registers */
return sam_spion_vbase(port);
}
else
{
/* Return the base address of the un-secured PIO registers */
return sam_pion_vbase(port);
}
}
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_configcommon
*
* Description:
* Configure common PIO pin settings based on bit-encoded description of
* the pin.
*
****************************************************************************/
static uint32_t sam_configcommon(pio_pinset_t cfgset)
{
uint32_t regval = 0;
/* Enable/disable the pull-up as requested
* NOTE: Control of the pull-up resistor is possible regardless of the
* configuration of the I/O line (Input, Output, Open-drain).
*/
if ((cfgset & PIO_CFG_PULLUP) != 0)
{
regval |= PIO_CFGR_PUEN;
}
/* Enable/disable the pull-down as requested */
if ((cfgset & PIO_CFG_PULLDOWN) != 0)
{
regval |= PIO_CFGR_PDEN;
}
/* Check if filtering should be enabled.
* NOTE: Input filtering and Schmitt triggering apply only to inputs.
*/
if ((cfgset & PIO_CFG_DEGLITCH) != 0)
{
if ((cfgset & PIO_CFG_SLOWCLK) != 0)
{
regval |= (PIO_CFGR_IFEN | PIO_CFG_SLOWCLK);
}
else
{
regval |= PIO_CFGR_IFEN;
}
}
/* Enable/disable the Schmitt trigger inputs.
* NOTE: Input filtering and Schmitt triggering apply only to inputs.
*/
if ((cfgset & PIO_CFG_SCHMITT) != 0)
{
regval |= PIO_CFGR_SCHMITT;
}
/* Enable the open drain driver if requested.
* NOTE: The open drain apply option applies only to output and
* peripheral pins.
*/
if ((cfgset & PIO_CFG_OPENDRAIN) != 0)
{
regval |= PIO_CFGR_OPD;
}
/* Select I/O drive.
* REVISIT: Doesn't rive strength apply only to output and peripheral
* pins as well?
*/
switch (cfgset & PIO_DRIVE_MASK)
{
default:
case PIO_DRIVE_LOW:
regval |= PIO_CFGR_DRVSTR_LOW;
break;
case PIO_DRIVE_MEDIUM:
regval |= PIO_CFGR_DRVSTR_MED;
break;
case PIO_DRIVE_HIGH:
regval |= PIO_CFGR_DRVSTR_HIGH;
break;
}
/* Select Input Event selection.
* NOTE: Only applies to input pins
*/
switch (cfgset & PIO_INT_MASK)
{
default:
case PIO_INT_NONE:
break;
case PIO_INT_FALLING:
regval |= PIO_CFGR_EVTSEL_FALLING;
break;
case PIO_INT_RISING:
regval |= PIO_CFGR_EVTSEL_RISING;
break;
case PIO_INT_BOTHEDGES:
regval |= PIO_CFGR_EVTSEL_BOTH;
break;
case PIO_INT_LOWLEVEL:
regval |= PIO_CFGR_EVTSEL_LOW;
break;
case PIO_INT_HIGHLEVEL:
regval |= PIO_CFGR_EVTSEL_HIGH;
break;
}
return regval;
}
/****************************************************************************
* 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)
{
uint32_t regval;
/* Disable interrupts on the pin */
putreg32(pin, base + SAM_PIO_IDR_OFFSET);
/* Select GPIO input */
regval = sam_configcommon(cfgset);
regval |= (PIO_CFGR_FUNC_GPIO | PIO_CFGR_DIR_INPUT);
/* Clear some output only bits. Mostly this just simplifies debug. */
putreg32(pin, base + SAM_PIO_CODR_OFFSET);
/* Configure the pin as an input and enable the PIO function */
putreg32(regval, base + SAM_PIO_CFGR_OFFSET);
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)
{
uint32_t regval;
/* Disable interrupts on the pin */
putreg32(pin, base + SAM_PIO_IDR_OFFSET);
/* Select GPIO output */
regval = sam_configcommon(cfgset);
regval = (PIO_CFGR_FUNC_GPIO | PIO_CFGR_DIR_OUTPUT);
/* 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(regval, base + SAM_PIO_CFGR_OFFSET);
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;
unsigned int periph;
/* Disable interrupts on the pin */
putreg32(pin, base + SAM_PIO_IDR_OFFSET);
/* Select the peripheral function. The Direction bit does not apply to
* peripherals.
*/
regval = sam_configcommon(cfgset);
periph = ((cfgset & PIO_MODE_MASK) - PIO_ANALOG) >> PIO_MODE_SHIFT;
regval |= PIO_CFGR_FUNC_PERIPH(periph);
/* Clear some output only bits. Mostly this just simplifies debug. */
putreg32(pin, base + SAM_PIO_CODR_OFFSET);
/* Configure the pin as a peripheral */
putreg32(regval, base + SAM_PIO_CFGR_OFFSET);
return OK;
}
/****************************************************************************
* Public 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;
/* Get the base address and pin mask associated with this pin
* configuration
*/
base = sam_piobase(cfgset);
if (base == 0)
{
return -EINVAL;
}
pin = sam_piopin(cfgset);
/* Disable interrupts to prohibit re-entrance. */
flags = enter_critical_section();
/* Enable writing to PIO registers.
*
* The following registers are write-protected when WPEN is set in
* PIO_WPMR:
* - PIO Mask Register
* - PIO Configuration Register
* The following registers are write-protected when WPEN is set in
* S_PIO_WPMR:
* - Secure PIO Mask Register
* - Secure PIO Configuration Register
* - Secure PIO Slow Clock Divider Debouncing Register
* The following registers are write-protected when WPITEN is set in
* PIO_WPMR:
* - PIO Interrupt Enable Register
* - PIO Interrupt Disable Register
* The following registers are write-protected when WPITEN is set in
* S_PIO_WPMR:
* - Secure PIO Interrupt Enable Register
* - Secure PIO Interrupt Disable Register
*
* I suspect that the default state is the WPMR is unprotected, so these
* operations could probably all be avoided.
*/
putreg32(PIO_WPMR_WPKEY, SAM_PIO_WPMR);
putreg32(PIO_WPMR_WPKEY, SAM_SPIO_WPMR);
/* Select the secure or un-secured PIO operation */
#if 0
if (sam_issecure(cfgset))
{
putreg32(pin, base + SAM_SPIO_SIOSR_OFFSET);
}
else
#endif
{
putreg32(pin, base + SAM_SPIO_SIONR_OFFSET);
}
/* Set the mask register to modify only the specific pin being
* configured.
*/
putreg32(pin, base + SAM_PIO_MSKR_OFFSET);
/* Put the pin in an initial state -- a vanilla input pin */
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_ANALOG:
/* REVISIT */
ret = OK;
break;
case PIO_PERIPHA:
case PIO_PERIPHB:
case PIO_PERIPHC:
case PIO_PERIPHD:
case PIO_PERIPHE:
case PIO_PERIPHF:
case PIO_PERIPHG:
ret = sam_configperiph(base, pin, cfgset);
break;
default:
ret = -EINVAL;
break;
}
/* Disable writing to PIO registers */
putreg32(PIO_WPMR_WPEN | PIO_WPMR_WPITEN | PIO_WPMR_WPKEY, SAM_PIO_WPMR);
putreg32(PIO_WPMR_WPEN | PIO_WPMR_WPITEN | PIO_WPMR_WPKEY, SAM_SPIO_WPMR);
leave_critical_section(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 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 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. Needed only for SAMA5D3/D4 compatibility. For the
* SAMA5D2, there is a common clock for all PIO ports and that clock is
* always enabled.
*
****************************************************************************/
void sam_pio_forceclk(pio_pinset_t pinset, bool enable)
{
}
/****************************************************************************
* Function: sam_dumppio
*
* Description:
* Dump all PIO registers associated with the base address of the provided
* pinset.
*
****************************************************************************/
#ifdef CONFIG_DEBUG_GPIO_INFO
int sam_dumppio(uint32_t pinset, const char *msg)
{
irqstate_t flags;
uintptr_t base;
unsigned int port;
bool secure;
/* Get the base address associated with the PIO port */
port = (pinset & PIO_PORT_MASK) >> PIO_PORT_SHIFT;
base = sam_piobase(pinset);
secure = sam_issecure(pinset);
/* The following requires exclusive access to the PIO registers */
flags = enter_critical_section();
if (secure)
{
gpioinfo("SPIO%c pinset: %08x base: %08x -- %s\n",
g_portchar[port], pinset, base, msg);
}
else
{
gpioinfo("PIO%c pinset: %08x base: %08x -- %s\n",
g_portchar[port], pinset, base, msg);
}
gpioinfo(" MSKR: %08x CFGR: %08x PDSR: %08x LOCKSR: %08x\n",
getreg32(base + SAM_PIO_MSKR_OFFSET),
getreg32(base + SAM_PIO_CFGR_OFFSET),
getreg32(base + SAM_PIO_PDSR_OFFSET),
getreg32(base + SAM_PIO_LOCKSR_OFFSET));
gpioinfo(" ODSR: %08x IMR: %08x ISR: %08x\n",
getreg32(base + SAM_PIO_ODSR_OFFSET),
getreg32(base + SAM_PIO_IMR_OFFSET),
getreg32(base + SAM_PIO_ISR_OFFSET));
if (secure)
{
gpioinfo(" SCDR: %08x WPMR: %08x WPSR: %08x IOSSR: %08x\n",
getreg32(SAM_SPIO_SCDR),
getreg32(SAM_SPIO_WPMR),
getreg32(SAM_SPIO_WPSR),
getreg32(base + SAM_SPIO_IOSSR_OFFSET));
}
else
{
gpioinfo(" WPMR: %08x WPSR: %08x\n",
getreg32(SAM_PIO_WPMR), getreg32(SAM_PIO_WPSR));
}
leave_critical_section(flags);
return OK;
}
#endif