1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 14:33:52 +01:00

cpu/msp430: add periph_gpio_ll

This commit is contained in:
Marian Buschsieweke 2024-05-03 15:25:32 +02:00
parent 5db461c28b
commit 0ce7b8dfde
No known key found for this signature in database
GPG Key ID: 77AA882EC78084E6
11 changed files with 569 additions and 88 deletions

View File

@ -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

View File

@ -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

View File

@ -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
*/

View File

@ -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
*

View File

@ -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 <marian.buschsieweke@posteo.net>
*/
#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 */
/** @} */

View File

@ -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

View File

@ -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.

View File

@ -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
*/

View File

@ -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);

View File

@ -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)

198
cpu/msp430/periph/gpio_ll.c Normal file
View File

@ -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 <marian.buschsieweke@posteo.net>
*
* @}
*/
#include <errno.h>
#include <stdbool.h>
#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;
}