diff --git a/cpu/msp430/Makefile.dep b/cpu/msp430/Makefile.dep index fd5f4e6f34..217815dc1f 100644 --- a/cpu/msp430/Makefile.dep +++ b/cpu/msp430/Makefile.dep @@ -5,5 +5,10 @@ ifneq (,$(filter newlib,$(USEMODULE))) DEFAULT_MODULE += newlib_nano endif +ifneq (,$(filter periph_gpio,$(USEMODULE))) + # the legacy periph_gpio driver uses gpio_port from periph_gpio_ll + FEATURES_REQUIRED += periph_gpio_ll +endif + # Make calls to malloc and friends thread-safe USEMODULE += malloc_thread_safe diff --git a/cpu/msp430/Makefile.features b/cpu/msp430/Makefile.features index 5ff6154290..d471c01e38 100644 --- a/cpu/msp430/Makefile.features +++ b/cpu/msp430/Makefile.features @@ -3,6 +3,8 @@ CPU_CORE = msp430 ifneq (,$(filter msp430f2% msp430g2%,$(CPU_MODEL))) CPU_FAM := msp430_f2xx_g2xx + FEATURES_PROVIDED += periph_gpio_ll_input_pull_down + FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_spi_reconfigure endif @@ -20,3 +22,6 @@ FEATURES_PROVIDED += periph_flashpage_in_address_space FEATURES_PROVIDED += periph_flashpage_pagewise FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += periph_timer_query_freqs + +FEATURES_PROVIDED += periph_gpio_ll +FEATURES_PROVIDED += periph_gpio_ll_switch_dir diff --git a/cpu/msp430/include/f2xx_g2xx/msp430_regs.h b/cpu/msp430/include/f2xx_g2xx/msp430_regs.h index f51e368acd..9b4608553c 100644 --- a/cpu/msp430/include/f2xx_g2xx/msp430_regs.h +++ b/cpu/msp430/include/f2xx_g2xx/msp430_regs.h @@ -57,6 +57,31 @@ extern "C" { #define MSP430_USCI_B_FROM_USCI_A(usci_a) \ ((msp430_usci_b_t *)((uintptr_t)(usci_a) + MSP430_USCI_A_B_OFFSET)) +/** + * @brief GPIO Port 1/2 (with interrupt functionality) + */ +typedef struct { + msp430_port_t base; /**< common GPIO port registers */ + REG8 IFG; /**< interrupt flag */ + REG8 IES; /**< interrupt edge select */ + REG8 IE; /**< interrupt enable */ + REG8 SEL; /**< alternative function select */ + REG8 REN; /**< pull resistor enable */ +} msp430_port_p1_p2_t; + +/** + * @brief GPIO Port 7/8 (different register layout than Ports 1-6) + */ +typedef struct { + REG8 IN; /**< input data */ + uint8_t _padding1; /**< unrelated I/O */ + REG8 OD; /**< output data */ + uint8_t _padding2; /**< unrelated I/O */ + REG8 DIR; /**< pin direction */ + uint8_t _padding3; /**< unrelated I/O */ + REG8 SEL; /**< alternative function select */ +} msp430_port_p7_p8_t; + /** * @brief Universal Serial Control Interface Type A (USCI_A) Registers */ diff --git a/cpu/msp430/include/f2xx_g2xx/periph_cpu.h b/cpu/msp430/include/f2xx_g2xx/periph_cpu.h index 9f88879a93..65f39ed8a1 100644 --- a/cpu/msp430/include/f2xx_g2xx/periph_cpu.h +++ b/cpu/msp430/include/f2xx_g2xx/periph_cpu.h @@ -183,6 +183,16 @@ typedef struct { const msp430_usci_spi_params_t *spi; /**< The SPI configuration to use */ } spi_conf_t; +/** + * @brief Register map of GPIO PORT 7 + */ +extern msp430_port_p7_p8_t PORT_7; + +/** + * @brief Register map of GPIO PORT 8 + */ +extern msp430_port_p7_p8_t PORT_8; + /** * @brief Acquire and initialize USCI for use a SPI/UART peripheral * diff --git a/cpu/msp430/include/gpio_ll_arch.h b/cpu/msp430/include/gpio_ll_arch.h new file mode 100644 index 0000000000..4fb1838d7a --- /dev/null +++ b/cpu/msp430/include/gpio_ll_arch.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2024 Marian Buschsieweke + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_msp430 + * @ingroup drivers_periph_gpio_ll + * @{ + * + * @file + * @brief CPU specific part of the Peripheral GPIO Low-Level API + * + * @author Marian Buschsieweke + */ + +#ifndef GPIO_LL_ARCH_H +#define GPIO_LL_ARCH_H + +#include "cpu.h" +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DOXYGEN /* hide implementation specific details from Doxygen */ + +/* the memory layout of all GPIO peripherals is compatible, but the location + * in the address space is pretty much random */ + +#define GPIO_PORT_1 ((gpio_port_t)&PORT_1.base) +#define GPIO_PORT_2 ((gpio_port_t)&PORT_2.base) +#define GPIO_PORT_3 ((gpio_port_t)&PORT_3.base) +#define GPIO_PORT_4 ((gpio_port_t)&PORT_4.base) +#define GPIO_PORT_5 ((gpio_port_t)&PORT_5.base) +#define GPIO_PORT_6 ((gpio_port_t)&PORT_6.base) +/* Port 7 and 8 have different memory layout and are only available on F2xx/G2xx + * MCUs */ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) +# define GPIO_PORT_7 ((gpio_port_t)&PORT_7) +# define GPIO_PORT_8 ((gpio_port_t)&PORT_8) +#endif + +/* IMPORTANT IMPLEMENTATION INFO + * ============================= + * + * - MSP430 F2xx/G2xx do have PORT 7 and PORT 8, but those have an incompatible + * memory layout compared to the other ports. Hence, they need extra handling. + * However, constant folding should get ride of the branch and overhead if the + * GPIO port is a compile time constant + * - MSP430 has bit manipulation instructions that work on memory. E.g. + * `BIC.B %[mask], @%[ptr]` will implement `*ptr &= ~(mask)` in a single + * instruction. Same for setting or XORing bits. Hence, the code below + * may often look like it is missing `irq_disable()` ... `irq_restore()`, but + * in fact will be atomic due to the MSP430 instruction set. + */ + +gpio_port_t gpio_port(uword_t num); + +static inline uword_t gpio_ll_read(gpio_port_t port) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + const msp430_port_p7_p8_t *p = (void *)port; + return p->IN; + } +#endif + const msp430_port_t *p = (void *)port; + return p->IN; +} + +static inline uword_t gpio_ll_read_output(gpio_port_t port) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + const msp430_port_p7_p8_t *p = (void *)port; + return p->OD; + } +#endif + const msp430_port_t *p = (void *)port; + return p->OD; +} + +static inline void gpio_ll_set(gpio_port_t port, uword_t mask) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD |= mask; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD |= mask; +} + +static inline void gpio_ll_clear(gpio_port_t port, uword_t mask) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD &= ~(mask); + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD &= ~(mask); +} + +static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD ^= mask; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD ^= mask; +} + +static inline void gpio_ll_write(gpio_port_t port, uword_t value) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD = value; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD = value; +} + +static inline gpio_port_t gpio_get_port(gpio_t pin) +{ + return gpio_port(gpio_get_pin_num(pin)); +} + +static inline uint8_t gpio_get_pin_num(gpio_t pin) +{ + return pin >> 8; +} + +static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->DIR |= outputs; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->DIR |= outputs; +} + +static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->DIR &= ~(inputs); + return; + } +#endif + msp430_port_t *p = (void *)port; + p->DIR &= ~(inputs); +} + +static inline gpio_port_t gpio_port_pack_addr(void *addr) +{ + return (gpio_port_t)addr; +} + +static inline void * gpio_port_unpack_addr(gpio_port_t port) +{ + if (port < RAMSTART) { + return NULL; + } + + return (void *)port; +} + +static inline bool is_gpio_port_num_valid(uint_fast8_t num) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + return (num > 0) && (num <= 8); +#else + return (num > 0) && (num <= 6); +#endif +} + +uword_t gpio_port_num(gpio_port_t port); + +#endif /* DOXYGEN */ + +#ifdef __cplusplus +} +#endif + +#endif /* GPIO_LL_ARCH_H */ +/** @} */ diff --git a/cpu/msp430/include/msp430_regs_common.h b/cpu/msp430/include/msp430_regs_common.h index 9d3abd9e59..1b57f4fddc 100644 --- a/cpu/msp430/include/msp430_regs_common.h +++ b/cpu/msp430/include/msp430_regs_common.h @@ -92,17 +92,6 @@ typedef struct { REG8 DIR; /**< pin direction */ } msp430_port_t; -/** - * @brief GPIO Port 1/2 (with interrupt functionality) - */ -typedef struct { - msp430_port_t base; /**< common GPIO port registers */ - REG8 IFG; /**< interrupt flag */ - REG8 IES; /**< interrupt edge select */ - REG8 IE; /**< interrupt enable */ - REG8 SEL; /**< alternative function select */ -} msp430_port_p1_p2_t; - /** * @brief GPIO Port 3..6 (without interrupt functionality) */ @@ -124,67 +113,6 @@ typedef struct { REG16 CCR[7]; /**< capture compare channel values */ } msp430_timer_t; -/** - * @name MSP430 Common Peripheral Register Maps - * - * @details The addresses will be provided by the linker script using the - * vendor files. - * @{ - */ -/** - * @brief Register map of GPIO PORT 1 - */ -extern msp430_port_p1_p2_t PORT_1; -/** - * @brief Register map of GPIO PORT 2 - */ -extern msp430_port_p1_p2_t PORT_2; -/** - * @brief Register map of GPIO PORT 3 - */ -extern msp430_port_p3_p6_t PORT_3; -/** - * @brief Register map of GPIO PORT 4 - */ -extern msp430_port_p3_p6_t PORT_4; -/** - * @brief Register map of GPIO PORT 5 - */ -extern msp430_port_p3_p6_t PORT_5; -/** - * @brief Register map of GPIO PORT 6 - */ -extern msp430_port_p3_p6_t PORT_6; - -/** - * @brief Register map of the timer A control registers - */ -extern msp430_timer_t TIMER_A; - -/** - * @brief IRQ flags for TIMER_A - * - * Called TAIV in the data sheet / vendor files. This shallow alias - * makes the name more readable and does impedance matching for the type - * (`volatile uint16_t` vs `volatile short`). - */ -extern REG16 TIMER_A_IRQFLAGS; - -/** - * @brief IRQ flags for TIMER_B - * - * Called TBIV in the data sheet / vendor files. This shallow alias - * makes the name more readable and does impedance matching for the type - * (`volatile uint16_t` vs `volatile short`). - */ -extern REG16 TIMER_B_IRQFLAGS; - -/** - * @brief Register map of the timer B control registers - */ -extern msp430_timer_t TIMER_B; -/** @} */ - #ifdef __cplusplus } #endif diff --git a/cpu/msp430/include/periph_cpu_common.h b/cpu/msp430/include/periph_cpu_common.h index f23099cfd0..a3f5010d01 100644 --- a/cpu/msp430/include/periph_cpu_common.h +++ b/cpu/msp430/include/periph_cpu_common.h @@ -64,6 +64,11 @@ typedef uint16_t gpio_t; */ #define TIMER_CHANNEL_NUMOF 7 +/** + * @brief Lowest address of the RAM, peripherals are below + */ +#define RAMSTART 0x200 + /** * @name Override flank selection values * @{ @@ -91,6 +96,42 @@ enum { P6 = 6, /**< PORT 6 */ }; +#ifndef DOXYGEN +#define HAVE_GPIO_STATE_T +typedef enum { + GPIO_INPUT, + GPIO_OUTPUT_PUSH_PULL, + GPIO_OUTPUT_OPEN_DRAIN, /**< not supported */ + GPIO_OUTPUT_OPEN_SOURCE, /**< not supported */ + GPIO_USED_BY_PERIPHERAL, /**< not supported */ + GPIO_DISCONNECT = GPIO_INPUT, +} gpio_state_t; + +#define HAVE_GPIO_SLEW_T +typedef enum { + GPIO_SLEW_SLOWEST = 0, + GPIO_SLEW_SLOW = 0, + GPIO_SLEW_FAST = 0, + GPIO_SLEW_FASTEST = 0, +} gpio_slew_t; + +#define HAVE_GPIO_PULL_STRENGTH_T +typedef enum { + GPIO_PULL_WEAKEST = 0, + GPIO_PULL_WEAK = 0, + GPIO_PULL_STRONG = 0, + GPIO_PULL_STRONGEST = 0 +} gpio_pull_strength_t; + +#define HAVE_GPIO_DRIVE_STRENGTH_T +typedef enum { + GPIO_DRIVE_WEAKEST = 0, + GPIO_DRIVE_WEAK = 0, + GPIO_DRIVE_STRONG = 0, + GPIO_DRIVE_STRONGEST = 0 +} gpio_drive_strength_t; +#endif /* !DOXYGEN */ + /** * @brief Enable or disable a pin to be used by peripheral modules * @@ -337,6 +378,68 @@ typedef struct { msp430_timer_clock_source_t clock_source; /**< Clock source to use */ } timer_conf_t; +/** + * @name MSP430 Common Peripheral Register Maps + * + * @details The addresses will be provided by the linker script using the + * vendor files. + * @{ + */ +/** + * @brief Register map of GPIO PORT 1 + */ +extern msp430_port_p1_p2_t PORT_1; +/** + * @brief Register map of GPIO PORT 2 + */ +extern msp430_port_p1_p2_t PORT_2; +/** + * @brief Register map of GPIO PORT 3 + */ +extern msp430_port_p3_p6_t PORT_3; +/** + * @brief Register map of GPIO PORT 4 + */ +extern msp430_port_p3_p6_t PORT_4; +/** + * @brief Register map of GPIO PORT 5 + */ +extern msp430_port_p3_p6_t PORT_5; +/** + * @brief Register map of GPIO PORT 6 + */ +extern msp430_port_p3_p6_t PORT_6; + +/** + * @brief Register map of the timer A control registers + */ +extern msp430_timer_t TIMER_A; + +/** + * @brief IRQ flags for TIMER_A + * + * Called TAIV in the data sheet / vendor files. This shallow alias + * makes the name more readable and does impedance matching for the type + * (`volatile uint16_t` vs `volatile short`). + */ +extern REG16 TIMER_A_IRQFLAGS; + +/** + * @brief IRQ flags for TIMER_B + * + * Called TBIV in the data sheet / vendor files. This shallow alias + * makes the name more readable and does impedance matching for the type + * (`volatile uint16_t` vs `volatile short`). + */ +extern REG16 TIMER_B_IRQFLAGS; + +/** + * @brief Register map of the timer B control registers + */ +extern msp430_timer_t TIMER_B; +/** @} */ + + /** * @brief Initialize the basic clock system to provide the main clock, * the subsystem clock, and the auxiliary clock. diff --git a/cpu/msp430/include/x1xx/msp430_regs.h b/cpu/msp430/include/x1xx/msp430_regs.h index 02b3c50530..461a586a0e 100644 --- a/cpu/msp430/include/x1xx/msp430_regs.h +++ b/cpu/msp430/include/x1xx/msp430_regs.h @@ -31,6 +31,17 @@ extern "C" { #endif +/** + * @brief GPIO Port 1/2 (with interrupt functionality) + */ +typedef struct { + msp430_port_t base; /**< common GPIO port registers */ + REG8 IFG; /**< interrupt flag */ + REG8 IES; /**< interrupt edge select */ + REG8 IE; /**< interrupt enable */ + REG8 SEL; /**< alternative function select */ +} msp430_port_p1_p2_t; + /** * @brief USART (UART, SPI and I2C) Registers */ diff --git a/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld b/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld index cb90d22587..e09994388d 100644 --- a/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld +++ b/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld @@ -4,3 +4,5 @@ PROVIDE(USCI_A0 = UCA0ABCTL); PROVIDE(USCI_A1 = UCA1ABCTL); PROVIDE(USCI_B0 = UCB0CTL0); PROVIDE(USCI_B1 = UCB1CTL0); +PROVIDE(PORT_7 = P7IN); +PROVIDE(PORT_8 = P8IN); diff --git a/cpu/msp430/periph/gpio.c b/cpu/msp430/periph/gpio.c index d1800bb8bc..484606310a 100644 --- a/cpu/msp430/periph/gpio.c +++ b/cpu/msp430/periph/gpio.c @@ -23,6 +23,7 @@ #include "container.h" #include "cpu.h" #include "periph/gpio.h" +#include "periph/gpio_ll.h" /** * @brief Number of possible interrupt lines: 2 ports * 8 pins @@ -36,22 +37,7 @@ static msp430_port_t *_port(gpio_t pin) { - switch (pin >> 8) { - case 1: - return &PORT_1.base; - case 2: - return &PORT_2.base; - case 3: - return &PORT_3.base; - case 4: - return &PORT_4.base; - case 5: - return &PORT_5.base; - case 6: - return &PORT_6.base; - default: - return NULL; - } + return (msp430_port_t *)gpio_port(pin >> 8); } static inline uint8_t _pin(gpio_t pin) diff --git a/cpu/msp430/periph/gpio_ll.c b/cpu/msp430/periph/gpio_ll.c new file mode 100644 index 0000000000..772261fb30 --- /dev/null +++ b/cpu/msp430/periph/gpio_ll.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2024 Marian Buschsieweke + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_msp430 + * @ingroup drivers_periph_gpio_ll + * @{ + * + * @file + * @brief Peripheral GPIO Low-Level API implementation for the MSP430 GPIO peripheral + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include + +#include "periph/gpio_ll.h" + +gpio_port_t gpio_port(uword_t num) +{ + switch (num) { + case 1: + return (gpio_port_t)&PORT_1.base; + case 2: + return (gpio_port_t)&PORT_2.base; + case 3: + return (gpio_port_t)&PORT_3.base; + case 4: + return (gpio_port_t)&PORT_4.base; + case 5: + return (gpio_port_t)&PORT_5.base; + case 6: + return (gpio_port_t)&PORT_6.base; + default: + return 0; + } +} + +uword_t gpio_port_num(gpio_port_t port) +{ + /* we need to hard-code the values here to have constant initializers in + * the look-up table :-/ */ + static const uint8_t map[] = { + 0x20, /* == GPIO_PORT_1 */ + 0x28, /* == GPIO_PORT_2 */ + 0x10, /* == GPIO_PORT_3 */ + 0x1C, /* == GPIO_PORT_4 */ + 0x30, /* == GPIO_PORT_5 */ + 0x34, /* == GPIO_PORT_6 */ +#if CPU_FAM_MSP430_F2XX_G2XX + 0x38, /* == GPIO_PORT_7 */ + 0x39, /* == GPIO_PORT_8 */ +#endif + }; + + for (unsigned i = 0; i < ARRAY_SIZE(map); i++) { + if (port == (gpio_port_t)map[i]) { + return i + 1; + } + } + return 0; +} + +#if CPU_FAM_MSP430_F2XX_G2XX +static void _set_pull(unsigned port_num, uword_t mask, bool enable) +{ + volatile uint8_t *ren_reg; + switch (port_num) { + case 1: + ren_reg = &PORT_1.REN; + break; + case 2: + ren_reg = &PORT_2.REN; + break; + default: + ren_reg = &P3REN; + ren_reg += (port_num - 3); + } + + if (enable) { + *ren_reg |= mask; + } + else { + *ren_reg &= ~(mask); + } +} + +static bool _get_pull(unsigned port_num, uword_t mask) +{ + volatile uint8_t *ren_reg; + switch (port_num) { + case 1: + ren_reg = &PORT_1.REN; + break; + case 2: + ren_reg = &PORT_2.REN; + break; + default: + ren_reg = &P3REN; + ren_reg += (port_num - 3); + } + + return*ren_reg & mask; +} +#endif + +int gpio_ll_init(gpio_port_t port, uint8_t pin, gpio_conf_t conf) +{ + msp430_port_t *p = (void *)port; + + if (!IS_ACTIVE(CPU_FAM_MSP430_F2XX_G2XX) && (conf.pull != GPIO_FLOATING)) { + return -ENOTSUP; + } + + switch (conf.state) { + case GPIO_INPUT: + case GPIO_OUTPUT_PUSH_PULL: + break; + default: + return -ENOTSUP; + } + + uword_t mask = 1U << pin; + bool initial_value = conf.initial_value; + +#if CPU_FAM_MSP430_F2XX_G2XX + unsigned port_num = gpio_port_num(port); + switch (conf.pull) { + default: + return -ENOTSUP; + case GPIO_FLOATING: + _set_pull(port_num, mask, false); + break; + case GPIO_PULL_UP: + _set_pull(port_num, mask, true); + initial_value = true; + break; + case GPIO_PULL_DOWN: + _set_pull(port_num, mask, true); + initial_value = false; + break; + } +#endif + + if (initial_value) { + gpio_ll_set(port, mask); + } + else { + gpio_ll_clear(port, mask); + } + + if (conf.state == GPIO_OUTPUT_PUSH_PULL) { + /* No need to disable IRQs, the BIS.B instruction is atomically */ + p->DIR |= mask; + } + else { + /* No need to disable IRQs, the BIC.B instruction is atomically */ + p->DIR &= ~mask; + } + + return 0; +} + +gpio_conf_t gpio_ll_query_conf(gpio_port_t port, uint8_t pin) +{ + msp430_port_t *p = (void *)port; + uword_t mask = 1U << pin; + gpio_conf_t result = { 0 }; + if (p->DIR & mask) { + result.state = GPIO_OUTPUT_PUSH_PULL; + result.initial_value = gpio_ll_read_output(port) & mask; + } + else { + result.state = GPIO_INPUT; + result.initial_value = gpio_ll_read(port) & mask; +#if CPU_FAM_MSP430_F2XX_G2XX + uword_t port_num = gpio_port_num(port); + if (_get_pull(port_num, mask)) { + if (gpio_ll_read_output(port) & mask) { + result.pull = GPIO_PULL_UP; + } + else { + result.pull = GPIO_PULL_DOWN; + } + } +#endif + } + + return result; +}