zephyr/drivers/pinctrl/pinctrl_mchp_xec.c
Manimaran A f8c8ee65be drivers: pinctrl: Microchip XEC PINCTRL glitch fix
Glitches were observed if a GPIO pin was configured by
ROM to a non-default state and then Zephyr PINCTRL
reconfigured the pin. The fix involves using the correct
PINCTRL YAML output enable and state flags. Reading the
current spin state and reflecting into new pin configuration
if the pin is output and the drive low/high properties are
not present. We also take advantage of GPIO hardware reflecing
the alternate output value in the parallel output bit before
enabling parallel output mode. Interpret boolean flags with
both enable and disable as do not touch if neither flag is
present. We give precedence to enable over disable if both
flags mistakenly appear. Note, PINCTRL always clears the
GPIO control input pad disable bit.

Signed-off-by: Manimaran A <manimaran.a@microchip.com>
2023-05-16 18:52:44 -04:00

192 lines
5.6 KiB
C

/*
* Copyright (c) 2016 Open-RnD Sp. z o.o.
* Copyright (c) 2021 Linaro Limited
* Copyright (c) 2021 Nordic Semiconductor ASA
* Copyright (c) 2021 Microchip Technology Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT microchip_xec_pinctrl
#include <zephyr/drivers/pinctrl.h>
#include <soc.h>
/*
* Microchip XEC: each GPIO pin has two 32-bit control register.
* The first 32-bit register contains all pin features except
* slew rate and driver strength in the second control register.
* We compute the register index from the beginning of the GPIO
* control address space which is the same range of the PINCTRL
* parent node. A zero value in the PINCTRL pinmux field means
* do not touch.
*/
static void config_drive_slew(struct gpio_regs * const regs, uint32_t idx, uint32_t conf)
{
uint32_t slew = (conf >> MCHP_XEC_SLEW_RATE_POS) & MCHP_XEC_SLEW_RATE_MSK0;
uint32_t drvstr = (conf >> MCHP_XEC_DRV_STR_POS) & MCHP_XEC_DRV_STR_MSK0;
uint32_t msk = 0, val = 0;
if (slew) {
msk |= MCHP_GPIO_CTRL2_SLEW_MASK;
/* slow slew value is 0 */
if (slew == MCHP_XEC_SLEW_RATE_FAST0) {
val |= MCHP_GPIO_CTRL2_SLEW_FAST;
}
}
if (drvstr) {
msk |= MCHP_GPIO_CTRL2_DRV_STR_MASK;
/* drive strength values are 0 based */
val |= ((drvstr - 1u) << MCHP_GPIO_CTRL2_DRV_STR_POS);
}
if (!msk) {
return;
}
regs->CTRL2[idx] = (regs->CTRL2[idx] & ~msk) | (val & msk);
}
/*
* Internal pulls feature:
* None, weak pull-up, weak pull-down, or repeater mode (both pulls enabled).
* We do not touch this field unless one or more of the DT booleans are set.
* If the no-bias boolean is set then disable internal pulls.
* If pull up and/or down is set enable the respective pull or both for what
* MCHP calls repeater(keeper) mode.
*/
static uint32_t prog_pud(uint32_t pcr1, uint32_t conf)
{
if (conf & BIT(MCHP_XEC_NO_PUD_POS)) {
pcr1 &= ~(MCHP_GPIO_CTRL_PUD_MASK);
pcr1 |= MCHP_GPIO_CTRL_PUD_NONE;
return pcr1;
}
if (conf & (BIT(MCHP_XEC_PU_POS) | BIT(MCHP_XEC_PD_POS))) {
pcr1 &= ~(MCHP_GPIO_CTRL_PUD_MASK);
if (conf & BIT(MCHP_XEC_PU_POS)) {
pcr1 |= MCHP_GPIO_CTRL_PUD_PU;
}
if (conf & BIT(MCHP_XEC_PD_POS)) {
pcr1 |= MCHP_GPIO_CTRL_PUD_PD;
}
}
return pcr1;
}
/*
* DT enable booleans take precedence over disable booleans.
* We initially clear alternate output disable allowing us to set output state
* in the control register. Hardware sets output state bit in both control and
* parallel output register bits. Alternate output disable only controls which
* register bit is writable by the EC. We also clear the input pad disable
* bit because we need the input pin state and we don't know if the requested
* alternate function is input or bi-directional.
* Note 1: hardware allows input and output to be simultaneously enabled.
* Note 2: hardware interrupt detection is only on the input path.
*/
static int xec_config_pin(uint32_t portpin, uint32_t conf, uint32_t altf)
{
struct gpio_regs * const regs = (struct gpio_regs * const)DT_INST_REG_ADDR(0);
uint32_t port = MCHP_XEC_PINMUX_PORT(portpin);
uint32_t pin = (uint32_t)MCHP_XEC_PINMUX_PIN(portpin);
uint32_t idx = 0u, pcr1 = 0u;
if (port >= NUM_MCHP_GPIO_PORTS) {
return -EINVAL;
}
/* MCHP XEC family is 32 pins per port */
idx = (port * 32U) + pin;
config_drive_slew(regs, idx, conf);
/* Clear alternate output disable and input pad disable */
regs->CTRL[idx] &= ~(BIT(MCHP_GPIO_CTRL_AOD_POS) | BIT(MCHP_GPIO_CTRL_INPAD_DIS_POS));
pcr1 = regs->CTRL[idx]; /* current configuration including pin input state */
pcr1 = regs->CTRL[idx]; /* read multiple times to allow propagation from pad */
pcr1 = regs->CTRL[idx]; /* Is this necessary? */
pcr1 = prog_pud(pcr1, conf);
/* Touch output enable. We always enable input */
if (conf & BIT(MCHP_XEC_OUT_DIS_POS)) {
pcr1 &= ~(MCHP_GPIO_CTRL_DIR_OUTPUT);
}
if (conf & BIT(MCHP_XEC_OUT_EN_POS)) {
pcr1 |= MCHP_GPIO_CTRL_DIR_OUTPUT;
}
/* Touch output state? Bit can be set even if the direction is input only */
if (conf & BIT(MCHP_XEC_OUT_LO_POS)) {
pcr1 &= ~BIT(MCHP_GPIO_CTRL_OUTVAL_POS);
}
if (conf & BIT(MCHP_XEC_OUT_HI_POS)) {
pcr1 |= BIT(MCHP_GPIO_CTRL_OUTVAL_POS);
}
/* Touch output buffer type? */
if (conf & BIT(MCHP_XEC_PUSH_PULL_POS)) {
pcr1 &= ~(MCHP_GPIO_CTRL_BUFT_OPENDRAIN);
}
if (conf & BIT(MCHP_XEC_OPEN_DRAIN_POS)) {
pcr1 |= MCHP_GPIO_CTRL_BUFT_OPENDRAIN;
}
/* Always touch power gate */
pcr1 &= ~MCHP_GPIO_CTRL_PWRG_MASK;
if (conf & BIT(MCHP_XEC_PIN_LOW_POWER_POS)) {
pcr1 |= MCHP_GPIO_CTRL_PWRG_OFF;
} else {
pcr1 |= MCHP_GPIO_CTRL_PWRG_VTR_IO;
}
/* Always touch MUX (alternate function) */
pcr1 &= ~MCHP_GPIO_CTRL_MUX_MASK;
pcr1 |= (uint32_t)((altf & MCHP_GPIO_CTRL_MUX_MASK0) << MCHP_GPIO_CTRL_MUX_POS);
/* Always touch invert of alternate function. Need another bit to avoid touching */
if (conf & BIT(MCHP_XEC_FUNC_INV_POS)) {
pcr1 |= BIT(MCHP_GPIO_CTRL_POL_POS);
} else {
pcr1 &= ~BIT(MCHP_GPIO_CTRL_POL_POS);
}
/* output state set in control & parallel regs */
regs->CTRL[idx] = pcr1;
/* make output state in control read-only in control and read-write in parallel reg */
regs->CTRL[idx] = pcr1 | BIT(MCHP_GPIO_CTRL_AOD_POS);
return 0;
}
int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt,
uintptr_t reg)
{
uint32_t portpin, pinmux, func;
int ret;
ARG_UNUSED(reg);
for (uint8_t i = 0U; i < pin_cnt; i++) {
pinmux = pins[i];
func = MCHP_XEC_PINMUX_FUNC(pinmux);
if (func >= MCHP_AFMAX) {
return -EINVAL;
}
portpin = MEC_XEC_PINMUX_PORT_PIN(pinmux);
ret = xec_config_pin(portpin, pinmux, func);
if (ret < 0) {
return ret;
}
}
return 0;
}