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 #include "pico.h"
11 #include "hardware/regs/addressmap.h"
12
13 /** \file address_mapped.h
14 * \defgroup hardware_base hardware_base
15 *
16 * Low-level types and (atomic) accessors for memory-mapped hardware registers
17 *
18 * `hardware_base` defines the low level types and access functions for memory mapped hardware registers. It is included
19 * by default by all other hardware libraries.
20 *
21 * The following register access typedefs codify the access type (read/write) and the bus size (8/16/32) of the hardware register.
22 * The register type names are formed by concatenating one from each of the 3 parts A, B, C
23
24 * A | B | C | Meaning
25 * ------|---|---|--------
26 * io_ | | | A Memory mapped IO register
27 * |ro_| | read-only access
28 * |rw_| | read-write access
29 * |wo_| | write-only access (can't actually be enforced via C API)
30 * | | 8| 8-bit wide access
31 * | | 16| 16-bit wide access
32 * | | 32| 32-bit wide access
33 *
34 * When dealing with these types, you will always use a pointer, i.e. `io_rw_32 *some_reg` is a pointer to a read/write
35 * 32 bit register that you can write with `*some_reg = value`, or read with `value = *some_reg`.
36 *
37 * RP2040 hardware is also aliased to provide atomic setting, clear or flipping of a subset of the bits within
38 * a hardware register so that concurrent access by two cores is always consistent with one atomic operation
39 * being performed first, followed by the second.
40 *
41 * See hw_set_bits(), hw_clear_bits() and hw_xor_bits() provide for atomic access via a pointer to a 32 bit register
42 *
43 * Additionally given a pointer to a structure representing a piece of hardware (e.g. `dma_hw_t *dma_hw` for the DMA controller), you can
44 * get an alias to the entire structure such that writing any member (register) within the structure is equivalent
45 * to an atomic operation via hw_set_alias(), hw_clear_alias() or hw_xor_alias()...
46 *
47 * For example `hw_set_alias(dma_hw)->inte1 = 0x80;` will set bit 7 of the INTE1 register of the DMA controller,
48 * leaving the other bits unchanged.
49 */
50
51 #define check_hw_layout(type, member, offset) static_assert(offsetof(type, member) == (offset), "hw offset mismatch")
52 #define check_hw_size(type, size) static_assert(sizeof(type) == (size), "hw size mismatch")
53
54 typedef volatile uint32_t io_rw_32;
55 typedef const volatile uint32_t io_ro_32;
56 typedef volatile uint32_t io_wo_32;
57 typedef volatile uint16_t io_rw_16;
58 typedef const volatile uint16_t io_ro_16;
59 typedef volatile uint16_t io_wo_16;
60 typedef volatile uint8_t io_rw_8;
61 typedef const volatile uint8_t io_ro_8;
62 typedef volatile uint8_t io_wo_8;
63
64 typedef volatile uint8_t *const ioptr;
65 typedef ioptr const const_ioptr;
66
67 // Untyped conversion alias pointer generation macros
68 #define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS | (uintptr_t)(addr)))
69 #define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS | (uintptr_t)(addr)))
70 #define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS | (uintptr_t)(addr)))
71
72 // Typed conversion alias pointer generation macros
73 #define hw_set_alias(p) ((typeof(p))hw_set_alias_untyped(p))
74 #define hw_clear_alias(p) ((typeof(p))hw_clear_alias_untyped(p))
75 #define hw_xor_alias(p) ((typeof(p))hw_xor_alias_untyped(p))
76
77 /*! \brief Atomically set the specified bits to 1 in a HW register
78 * \ingroup hardware_base
79 *
80 * \param addr Address of writable register
81 * \param mask Bit-mask specifying bits to set
82 */
hw_set_bits(io_rw_32 * addr,uint32_t mask)83 inline static void hw_set_bits(io_rw_32 *addr, uint32_t mask) {
84 *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask;
85 }
86
87 /*! \brief Atomically clear the specified bits to 0 in a HW register
88 * \ingroup hardware_base
89 *
90 * \param addr Address of writable register
91 * \param mask Bit-mask specifying bits to clear
92 */
hw_clear_bits(io_rw_32 * addr,uint32_t mask)93 inline static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) {
94 *(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask;
95 }
96
97 /*! \brief Atomically flip the specified bits in a HW register
98 * \ingroup hardware_base
99 *
100 * \param addr Address of writable register
101 * \param mask Bit-mask specifying bits to invert
102 */
hw_xor_bits(io_rw_32 * addr,uint32_t mask)103 inline static void hw_xor_bits(io_rw_32 *addr, uint32_t mask) {
104 *(io_rw_32 *) hw_xor_alias_untyped((volatile void *) addr) = mask;
105 }
106
107 /*! \brief Set new values for a sub-set of the bits in a HW register
108 * \ingroup hardware_base
109 *
110 * Sets destination bits to values specified in \p values, if and only if corresponding bit in \p write_mask is set
111 *
112 * Note: this method allows safe concurrent modification of *different* bits of
113 * a register, but multiple concurrent access to the same bits is still unsafe.
114 *
115 * \param addr Address of writable register
116 * \param values Bits values
117 * \param write_mask Mask of bits to change
118 */
hw_write_masked(io_rw_32 * addr,uint32_t values,uint32_t write_mask)119 inline static void hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) {
120 hw_xor_bits(addr, (*addr ^ values) & write_mask);
121 }
122
123 #endif
124