1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! Low level API for calling bootloader functions

use crate::Address;
use crate::*;

use cfg_if::cfg_if;
#[allow(unused_imports)]
use core::arch::asm;

/// Store a whole page into program memory by erasing the page, filling the buffer,
/// and writing the buffer to the program memory.  
/// `address` must be page aligned.
pub fn store_page<'a>(address: impl Into<Address>, data: impl Into<&'a DataPage>) {
    let page_address: Address = address.into();

    erase_page(page_address);
    copy_to_buffer(data);
    write_page(page_address);
    rww_enable();
}

/// Erase the page from program memory
///
/// The PCPAGE part of the address is used to address the page, the PCWORD part must be zero
#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
pub fn erase_page(address: impl Into<Address>) {
    let page_address: Address = address.into();
    let z_address: u16 = page_address.into_page_aligned().into();

    busy_wait();
    rampz(page_address.ramp());
    cfg_if! {
        if #[cfg(all(target_arch = "avr", not(doc)))] {
            unsafe {
                asm!(
                    "rcall {spm}",
                    spm = sym spm,
                    in("r24") PAGE_ERASE,
                    in("Z") z_address,
                );
            }
        }
    }
}

/// Write data to the page buffer
///
/// Only the PCWORD part of the address actually matters, the size of which varies according to SPM_PAGESIZE_BYTES
#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
pub fn fill_page(address: impl Into<Address>, data: u16) {
    let page_address: Address = address.into();
    let z_address: u16 = page_address.into();

    busy_wait();
    cfg_if! {
        if #[cfg(all(target_arch = "avr", not(doc)))] {
            unsafe {
                asm!(
                    "
                    movw r0 {data}
                    rcall {spm}
                    eor	r1, r1
                    ",
                    data = in(reg_iw) data,
                    spm = sym spm,
                    in("r24") PAGE_FILL,
                    in("Z") z_address,
                )
            }
        }
    }
}

/// Write the page from the buffer to the program memory
///
/// The PCPAGE part of the address is used to address the page, the PCWORD part must be zero
#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
pub fn write_page(address: impl Into<Address>) {
    let page_address: Address = address.into();
    let z_address: u16 = page_address.into_page_aligned().into();

    busy_wait();
    rampz(page_address.ramp());
    cfg_if! {
        if #[cfg(all(target_arch = "avr", not(doc)))] {
            unsafe {
                asm!(
                    "rcall {spm}",
                    spm = sym spm,
                    in("r24") PAGE_WRITE,
                    in("Z") z_address,
                )
            }
        }
    }
}

/// Fill the whole buffer at once
///
#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
pub fn copy_to_buffer<'a>(data: impl Into<&'a DataPage>) {
    busy_wait();
    rampz(0);
    cfg_if! {
        if #[cfg(all(target_arch = "avr", not(doc)))] {
            unsafe {
                asm!(
                    "
                    1:                       
                        ld      r0,         X+  // Load r0r1 pair with data from X pointer
                        ld      r1,         X+
                        rcall   {spm}           // call spm(PAGE_FILL) (r24 is always 1st byte argument)
                        adiw    Z,          2   // increment Z
                        subi    {words},    1   // decrement counter
                        brne    1b              // loop until counter reaches 0

                        clr	    r1
                    ",

                    words = inout(reg) SPM_PAGESIZE_WORDS as u8 => _,
                    spm = sym spm,
                    in("r24") PAGE_FILL,
                    inout("X") data.into().as_ptr() => _,
                    inout("Z") 0u16 => _,
                )
            }
        }
    }
}

#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
pub fn lock_bits_set(lock_bits: u8) {
    rampz(0);
    cfg_if! {
        if #[cfg(all(target_arch = "avr", not(doc)))] {
            let value = !lock_bits;
            unsafe {
                asm!(
                    "
                    mov r0 {value}
                    rcall {spm}
                    ",
                    spm = sym spm,
                    value = in(reg) value,
                    in("r24") LOCK_BITS_SET,
                    in("Z") 0x0001u16,
                )
            }
        }
    }
}

/// Re-enable the RWW section after programming, to enable it to be read
#[cfg(rww_enable)]
pub fn rww_enable() {
    spm(RWW_ENABLE);
}

/// Empty function for devices without a RWW section
#[cfg(not(rww_enable))]
pub fn rww_enable() {}

/// Wait for the current SPM operation to complete.
///
/// On devices with a RWW section, the CPU is not halted during the SPM operation if the RWW section is being written to.
/// Therefore it is important that we make sure the operation is complete before trying to do the next operation.
pub fn busy_wait() {
    cfg_if! {
        if #[cfg(all(target_arch = "avr", not(doc)))] {
           while unsafe { core::ptr::read_volatile(SPMCSR) } & PAGE_FILL != 0 {}
        }
    }
}

#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
extern "C" fn spm(spmcsr_val: u8) {
    cfg_if! {
        if #[cfg(all(target_arch = "avr", not(doc)))] {
            unsafe {
                core::ptr::write_volatile(SPMCSR, spmcsr_val);
                asm!("spm")
            }
        }
    }
}

#[cfg_attr(
    not(all(target_arch = "avr", extended_addressing)),
    allow(unused_variables)
)]
fn rampz(value: u8) {
    cfg_if! {
        if #[cfg(all(target_arch = "avr", extended_addressing, not(doc)))] {
            unsafe {
                core::ptr::write_volatile(crate::RAMPZ, value);
            }
        }
    }
}