|
| 1 | +/* |
| 2 | + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: BSD-3-Clause |
| 5 | + */ |
| 6 | + |
| 7 | +#ifndef _HARDWARE_ADDRESS_MAPPED_H |
| 8 | +#define _HARDWARE_ADDRESS_MAPPED_H |
| 9 | + |
| 10 | +#ifdef __cplusplus |
| 11 | +#include <modm/architecture/utils.hpp> |
| 12 | +#include <cstdint> |
| 13 | +#endif |
| 14 | + |
| 15 | +#define invalid_params_if(x, test) \ |
| 16 | + if (0) {} |
| 17 | +#define valid_params_if(x, test) \ |
| 18 | + if (0) {} |
| 19 | +#define hard_assert_if(x, test) \ |
| 20 | + if (0) {} |
| 21 | + |
| 22 | +#include "hardware/regs/addressmap.h" |
| 23 | + |
| 24 | +/** \file address_mapped.h |
| 25 | + * \defgroup hardware_base hardware_base |
| 26 | + * |
| 27 | + * Low-level types and (atomic) accessors for memory-mapped hardware registers |
| 28 | + * |
| 29 | + * `hardware_base` defines the low level types and access functions for memory mapped hardware |
| 30 | + registers. It is included |
| 31 | + * by default by all other hardware libraries. |
| 32 | + * |
| 33 | + * The following register access typedefs codify the access type (read/write) and the bus size |
| 34 | + (8/16/32) of the hardware register. |
| 35 | + * The register type names are formed by concatenating one from each of the 3 parts A, B, C |
| 36 | +
|
| 37 | + * A | B | C | Meaning |
| 38 | + * ------|---|---|-------- |
| 39 | + * io_ | | | A Memory mapped IO register |
| 40 | + * |ro_| | read-only access |
| 41 | + * |rw_| | read-write access |
| 42 | + * |wo_| | write-only access (can't actually be enforced via C API) |
| 43 | + * | | 8| 8-bit wide access |
| 44 | + * | | 16| 16-bit wide access |
| 45 | + * | | 32| 32-bit wide access |
| 46 | + * |
| 47 | + * When dealing with these types, you will always use a pointer, i.e. `io_rw_32 *some_reg` is a |
| 48 | + pointer to a read/write |
| 49 | + * 32 bit register that you can write with `*some_reg = value`, or read with `value = *some_reg`. |
| 50 | + * |
| 51 | + * RP2040 hardware is also aliased to provide atomic setting, clear or flipping of a subset of the |
| 52 | + bits within |
| 53 | + * a hardware register so that concurrent access by two cores is always consistent with one atomic |
| 54 | + operation |
| 55 | + * being performed first, followed by the second. |
| 56 | + * |
| 57 | + * See hw_set_bits(), hw_clear_bits() and hw_xor_bits() provide for atomic access via a pointer to |
| 58 | + a 32 bit register |
| 59 | + * |
| 60 | + * Additionally given a pointer to a structure representing a piece of hardware (e.g. `dma_hw_t |
| 61 | + *dma_hw` for the DMA controller), you can |
| 62 | + * get an alias to the entire structure such that writing any member (register) within the |
| 63 | + structure is equivalent |
| 64 | + * to an atomic operation via hw_set_alias(), hw_clear_alias() or hw_xor_alias()... |
| 65 | + * |
| 66 | + * For example `hw_set_alias(dma_hw)->inte1 = 0x80;` will set bit 7 of the INTE1 register of the |
| 67 | + DMA controller, |
| 68 | + * leaving the other bits unchanged. |
| 69 | + */ |
| 70 | + |
| 71 | +#ifdef __cplusplus |
| 72 | +extern "C" { |
| 73 | +#endif |
| 74 | + |
| 75 | +#define check_hw_layout(type, member, offset) \ |
| 76 | + static_assert(offsetof(type, member) == (offset), "hw offset mismatch") |
| 77 | +#define check_hw_size(type, size) static_assert(sizeof(type) == (size), "hw size mismatch") |
| 78 | + |
| 79 | +typedef volatile uint32_t io_rw_32; |
| 80 | +typedef const volatile uint32_t io_ro_32; |
| 81 | +typedef volatile uint32_t io_wo_32; |
| 82 | +typedef volatile uint16_t io_rw_16; |
| 83 | +typedef const volatile uint16_t io_ro_16; |
| 84 | +typedef volatile uint16_t io_wo_16; |
| 85 | +typedef volatile uint8_t io_rw_8; |
| 86 | +typedef const volatile uint8_t io_ro_8; |
| 87 | +typedef volatile uint8_t io_wo_8; |
| 88 | + |
| 89 | +typedef volatile uint8_t *const ioptr; |
| 90 | +typedef ioptr const const_ioptr; |
| 91 | + |
| 92 | +// A non-functional (empty) helper macro to help IDEs follow links from the autogenerated |
| 93 | +// hardware struct headers in hardware/structs/xxx.h to the raw register definitions |
| 94 | +// in hardware/regs/xxx.h. A preprocessor define such as TIMER_TIMEHW_OFFSET (a timer register |
| 95 | +// offset) is not generally clickable (in an IDE) if placed in a C comment, so |
| 96 | +// _REG_(TIMER_TIMEHW_OFFSET) is included outside of a comment instead |
| 97 | +#define _REG_(x) |
| 98 | + |
| 99 | +// Helper method used by hw_alias macros to optionally check input validity |
| 100 | +#define hw_alias_check_addr(addr) ((uintptr_t)(addr)) |
| 101 | +// can't use the following impl as it breaks existing static declarations using hw_alias, so would |
| 102 | +// be a backwards incompatibility |
| 103 | +// static __force_inline uint32_t hw_alias_check_addr(volatile void *addr) { |
| 104 | +// uint32_t rc = (uintptr_t)addr; |
| 105 | +// invalid_params_if(ADDRESS_ALIAS, rc < 0x40000000); // catch likely non HW pointer types |
| 106 | +// return rc; |
| 107 | +//} |
| 108 | + |
| 109 | +// Helper method used by xip_alias macros to optionally check input validity |
| 110 | +modm_always_inline static uint32_t |
| 111 | +xip_alias_check_addr(const void *addr) |
| 112 | +{ |
| 113 | + uint32_t rc = (uintptr_t)addr; |
| 114 | + valid_params_if(ADDRESS_ALIAS, rc >= XIP_MAIN_BASE && rc < XIP_NOALLOC_BASE); |
| 115 | + return rc; |
| 116 | +} |
| 117 | + |
| 118 | +// Untyped conversion alias pointer generation macros |
| 119 | +#define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS | hw_alias_check_addr(addr))) |
| 120 | +#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS | hw_alias_check_addr(addr))) |
| 121 | +#define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS | hw_alias_check_addr(addr))) |
| 122 | +#define xip_noalloc_alias_untyped(addr) ((void *)(XIP_NOALLOC_BASE | xip_alias_check_addr(addr))) |
| 123 | +#define xip_nocache_alias_untyped(addr) ((void *)(XIP_NOCACHE_BASE | xip_alias_check_addr(addr))) |
| 124 | +#define xip_nocache_noalloc_alias_untyped(addr) \ |
| 125 | + ((void *)(XIP_NOCACHE_NOALLOC_BASE | xip_alias_check_addr(addr))) |
| 126 | + |
| 127 | +// Typed conversion alias pointer generation macros |
| 128 | +#define hw_set_alias(p) ((typeof(p))hw_set_alias_untyped(p)) |
| 129 | +#define hw_clear_alias(p) ((typeof(p))hw_clear_alias_untyped(p)) |
| 130 | +#define hw_xor_alias(p) ((typeof(p))hw_xor_alias_untyped(p)) |
| 131 | +#define xip_noalloc_alias(p) ((typeof(p))xip_noalloc_alias_untyped(p)) |
| 132 | +#define xip_nocache_alias(p) ((typeof(p))xip_nocache_alias_untyped(p)) |
| 133 | +#define xip_nocache_noalloc_alias(p) ((typeof(p))xip_nocache_noalloc_alias_untyped(p)) |
| 134 | + |
| 135 | +/*! \brief Atomically set the specified bits to 1 in a HW register |
| 136 | + * \ingroup hardware_base |
| 137 | + * |
| 138 | + * \param addr Address of writable register |
| 139 | + * \param mask Bit-mask specifying bits to set |
| 140 | + */ |
| 141 | +modm_always_inline static void |
| 142 | +hw_set_bits(io_rw_32 *addr, uint32_t mask) |
| 143 | +{ |
| 144 | + *(io_rw_32 *)hw_set_alias_untyped((volatile void *)addr) = mask; |
| 145 | +} |
| 146 | + |
| 147 | +/*! \brief Atomically clear the specified bits to 0 in a HW register |
| 148 | + * \ingroup hardware_base |
| 149 | + * |
| 150 | + * \param addr Address of writable register |
| 151 | + * \param mask Bit-mask specifying bits to clear |
| 152 | + */ |
| 153 | +modm_always_inline static void |
| 154 | +hw_clear_bits(io_rw_32 *addr, uint32_t mask) |
| 155 | +{ |
| 156 | + *(io_rw_32 *)hw_clear_alias_untyped((volatile void *)addr) = mask; |
| 157 | +} |
| 158 | + |
| 159 | +/*! \brief Atomically flip the specified bits in a HW register |
| 160 | + * \ingroup hardware_base |
| 161 | + * |
| 162 | + * \param addr Address of writable register |
| 163 | + * \param mask Bit-mask specifying bits to invert |
| 164 | + */ |
| 165 | +modm_always_inline static void |
| 166 | +hw_xor_bits(io_rw_32 *addr, uint32_t mask) |
| 167 | +{ |
| 168 | + *(io_rw_32 *)hw_xor_alias_untyped((volatile void *)addr) = mask; |
| 169 | +} |
| 170 | + |
| 171 | +/*! \brief Set new values for a sub-set of the bits in a HW register |
| 172 | + * \ingroup hardware_base |
| 173 | + * |
| 174 | + * Sets destination bits to values specified in \p values, if and only if corresponding bit in \p |
| 175 | + * write_mask is set |
| 176 | + * |
| 177 | + * Note: this method allows safe concurrent modification of *different* bits of |
| 178 | + * a register, but multiple concurrent access to the same bits is still unsafe. |
| 179 | + * |
| 180 | + * \param addr Address of writable register |
| 181 | + * \param values Bits values |
| 182 | + * \param write_mask Mask of bits to change |
| 183 | + */ |
| 184 | +modm_always_inline static void |
| 185 | +hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) |
| 186 | +{ |
| 187 | + hw_xor_bits(addr, (*addr ^ values) & write_mask); |
| 188 | +} |
| 189 | + |
| 190 | +#ifdef __cplusplus |
| 191 | +} |
| 192 | +#endif |
| 193 | + |
| 194 | +#endif |
0 commit comments