/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Adrian Chadd * * 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 unmodified, 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ /* * This is a pinmux/gpio controller for the IPQ4018/IPQ4019. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qcom_tlmm_var.h" #include "qcom_tlmm_ipq4018_reg.h" #include "qcom_tlmm_ipq4018_hw.h" #include "gpio_if.h" /* * Set the pin function. This is a hardware and pin specific mapping. * * Returns 0 if OK, an errno if an error was encountered. */ int qcom_tlmm_ipq4018_hw_pin_set_function(struct qcom_tlmm_softc *sc, int pin, int function) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg &= ~(QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_MASK << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_SHIFT); reg |= (function & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_MASK) << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_SHIFT; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); return (0); } /* * Get the pin function. This is a hardware and pin specific mapping. * * Returns 0 if OK, an errno if a nerror was encountered. */ int qcom_tlmm_ipq4018_hw_pin_get_function(struct qcom_tlmm_softc *sc, int pin, int *function) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg = reg >> QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_SHIFT; reg &= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_MUX_MASK; *function = reg; return (0); } /* * Set the OE bit to be output. This assumes the port is configured * as a GPIO port. */ int qcom_tlmm_ipq4018_hw_pin_set_oe_output(struct qcom_tlmm_softc *sc, int pin) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OE_ENABLE; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); return (0); } /* * Set the OE bit to be input. This assumes the port is configured * as a GPIO port. */ int qcom_tlmm_ipq4018_hw_pin_set_oe_input(struct qcom_tlmm_softc *sc, int pin) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OE_ENABLE; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); return (0); } /* * Get the GPIO pin direction. is_output is set to true if the pin * is an output pin, false if it's set to an input pin. */ int qcom_tlmm_ipq4018_hw_pin_get_oe_state(struct qcom_tlmm_softc *sc, int pin, bool *is_output) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); *is_output = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OE_ENABLE); return (0); } /* * Set the given GPIO pin to the given value. */ int qcom_tlmm_ipq4018_hw_pin_set_output_value(struct qcom_tlmm_softc *sc, uint32_t pin, unsigned int value) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_IO)); if (value) reg |= QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; else reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_IO), reg); return (0); } /* * Get the input state of the current GPIO pin. */ int qcom_tlmm_ipq4018_hw_pin_get_output_value(struct qcom_tlmm_softc *sc, uint32_t pin, unsigned int *val) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_IO)); *val = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_IO_INPUT_STATUS); return (0); } /* * Get the input state of the current GPIO pin. */ int qcom_tlmm_ipq4018_hw_pin_get_input_value(struct qcom_tlmm_softc *sc, uint32_t pin, unsigned int *val) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_IO)); *val = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_IO_INPUT_STATUS); return (0); } /* * Toggle the current output pin value. */ int qcom_tlmm_ipq4018_hw_pin_toggle_output_value( struct qcom_tlmm_softc *sc, uint32_t pin) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_IO)); if ((reg & QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN) == 0) reg |= QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; else reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_IO_OUTPUT_EN; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_IO), reg); return (0); } /* * Configure the pull-up / pull-down top-level configuration. * * This doesn't configure the resistor values, just what's enabled/disabled. */ int qcom_tlmm_ipq4018_hw_pin_set_pupd_config( struct qcom_tlmm_softc *sc, uint32_t pin, qcom_tlmm_pin_pupd_config_t pupd) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg &= ~(QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_MASK << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT); switch (pupd) { case QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE: reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_DISABLE << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; break; case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN: reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLDOWN << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; break; case QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP: reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLUP << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; break; case QCOM_TLMM_PIN_PUPD_CONFIG_BUS_HOLD: reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_BUSHOLD << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; break; } GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); return (0); } /* * Fetch the current pull-up / pull-down configuration. */ int qcom_tlmm_ipq4018_hw_pin_get_pupd_config( struct qcom_tlmm_softc *sc, uint32_t pin, qcom_tlmm_pin_pupd_config_t *pupd) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg >>= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_SHIFT; reg &= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_MASK; switch (reg) { case QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_DISABLE: *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE; break; case QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLDOWN: *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_PULL_DOWN; break; case QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_PUPD_PULLUP: *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_PULL_UP; break; default: *pupd = QCOM_TLMM_PIN_PUPD_CONFIG_DISABLE; break; } return (0); } /* * Set the drive strength in mA. */ int qcom_tlmm_ipq4018_hw_pin_set_drive_strength( struct qcom_tlmm_softc *sc, uint32_t pin, uint8_t drv) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); /* Convert mA to hardware */ if (drv > 16 || drv < 2) return (EINVAL); drv = (drv / 2) - 1; reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg &= ~(QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_SHIFT << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_MASK); reg |= (drv & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_MASK) << QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_SHIFT; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); return (0); } /* * Get the drive strength in mA. */ int qcom_tlmm_ipq4018_hw_pin_get_drive_strength( struct qcom_tlmm_softc *sc, uint32_t pin, uint8_t *drv) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); *drv = (reg >> QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_SHIFT) & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_DRIVE_STRENGTH_MASK; *drv = (*drv + 1) * 2; return (0); } /* * Enable/disable whether this pin is passed through to a VM. */ int qcom_tlmm_ipq4018_hw_pin_set_vm( struct qcom_tlmm_softc *sc, uint32_t pin, bool enable) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_VM_ENABLE; if (enable) reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_VM_ENABLE; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); return (0); } /* * Get the VM configuration bit. */ int qcom_tlmm_ipq4018_hw_pin_get_vm( struct qcom_tlmm_softc *sc, uint32_t pin, bool *enable) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); *enable = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_VM_ENABLE); return (0); } /* * Enable/disable open drain. */ int qcom_tlmm_ipq4018_hw_pin_set_open_drain( struct qcom_tlmm_softc *sc, uint32_t pin, bool enable) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); reg &= ~QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OD_ENABLE; if (enable) reg |= QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OD_ENABLE; GPIO_WRITE(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL), reg); return (0); } /* * Get the open drain configuration bit. */ int qcom_tlmm_ipq4018_hw_pin_get_open_drain( struct qcom_tlmm_softc *sc, uint32_t pin, bool *enable) { uint32_t reg; GPIO_LOCK_ASSERT(sc); if (pin >= sc->gpio_npins) return (EINVAL); reg = GPIO_READ(sc, QCOM_TLMM_IPQ4018_REG_PIN(pin, QCOM_TLMM_IPQ4018_REG_PIN_CONTROL)); *enable = !! (reg & QCOM_TLMM_IPQ4018_REG_PIN_CONTROL_OD_ENABLE); return (0); }