aboutsummaryrefslogtreecommitdiffstats
path: root/src/atsam/sam4e_afec.c
blob: 33fb8e90533b9fe72ca5e3e8660ccc7fa248c87f (plain)
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// SAM4e8e Analog Front-End Converter (AFEC) support
//
// Copyright (C) 2018  Florian Heilmann <Florian.Heilmann@gmx.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.

#include "autoconf.h" // CONFIG_CLOCK_FREQ
#include "command.h" // shutdown
#include "gpio.h" // gpio_adc_setup
#include "internal.h" // GPIO, AFEC0
#include "sched.h" // sched_shutdown

#if CONFIG_MACH_SAME70
#include "same70_afec.h" // Fixes for upstream header changes
#endif

#define ADC_TEMPERATURE_PIN 0xfe
DECL_ENUMERATION("pin", "ADC_TEMPERATURE", ADC_TEMPERATURE_PIN);

static const uint8_t afec_pins[] = {
    //remove first channel, since it offsets the channel number: GPIO('A', 8),
    #if CONFIG_MACH_SAM4E
    GPIO('A', 17), GPIO('A', 18), GPIO('A', 19),
    GPIO('A', 20), GPIO('B', 0),  GPIO('B', 1), GPIO('C', 13),
    GPIO('C', 15), GPIO('C', 12), GPIO('C', 29), GPIO('C', 30),
    GPIO('C', 31), GPIO('C', 26), GPIO('C', 27), GPIO('C',0),
    ADC_TEMPERATURE_PIN,
    // AFEC1
    GPIO('B', 2), GPIO('B', 3), GPIO('A', 21), GPIO('A', 22),
    GPIO('C', 1), GPIO('C', 2), GPIO('C', 3), GPIO('C', 4),
    #elif CONFIG_MACH_SAME70
    GPIO('D', 30), GPIO('A', 21), GPIO('B', 3),  GPIO('E', 5),
    GPIO('E', 4),  GPIO('B', 2),  GPIO('A', 17), GPIO('A', 18),
    GPIO('A', 19), GPIO('A', 20), GPIO('B', 0),
    ADC_TEMPERATURE_PIN,
    // AFEC1
    GPIO('B', 1),  GPIO('C', 13), GPIO('C', 15), GPIO('C', 12),
    GPIO('C', 29), GPIO('C', 30), GPIO('C', 31), GPIO('C', 26),
    GPIO('C', 27), GPIO('C', 0),  GPIO('E', 3),  GPIO('E', 0),
    #endif
};

#if CONFIG_MACH_SAM4E
#define AFEC1_START 16 // The first 16 pins are on afec0
#define CFG_AFE_MR (AFE_MR_ANACH_ALLOWED | \
                    AFE_MR_PRESCAL(pclk / (2 * ADC_FREQ_MAX) - 1) | \
                    AFE_MR_SETTLING_AST3 | \
                    AFE_MR_TRACKTIM(2) | \
                    AFE_MR_TRANSFER(1) | \
                    AFE_MR_STARTUP_SUT64)
#define CFG_AFE_ACR AFE_ACR_IBCTL(1)
#define CFG_AFE_IDR 0xDF00FFFF
#define CFG_AFE_COCR (0x800 & AFE_COCR_AOFF_Msk)

#elif CONFIG_MACH_SAME70
#define AFEC1_START 12 // The first 12 pins are on afec0
#define CFG_AFE_MR (AFEC_MR_ONE | \
                    AFE_MR_PRESCAL (pclk / (ADC_FREQ_MAX) -1) | \
                    AFE_MR_TRANSFER(2) | \
                    AFE_MR_STARTUP_SUT64)
#define CFG_AFE_ACR (AFE_ACR_IBCTL(1) | AFEC_ACR_PGA0EN | AFEC_ACR_PGA1EN)
#define CFG_AFE_IDR 0x47000FFF
#define CFG_AFE_COCR (0x200 & AFE_COCR_AOFF_Msk)
#endif

static inline struct gpio_adc
pin_to_gpio_adc(uint8_t pin)
{
    int chan;
    for (chan=0; ; chan++) {
        if (chan >= ARRAY_SIZE(afec_pins))
            shutdown("Not a valid ADC pin");
        if (afec_pins[chan] == pin) {
            break;
        }
    }
    return (struct gpio_adc){ .chan=chan };
}

static inline Afec *
gpio_adc_to_afec(struct gpio_adc g)
{
    return (g.chan >= AFEC1_START ? AFEC1 : AFEC0);
}

static inline uint32_t
gpio_adc_to_afec_chan(struct gpio_adc g)
{
    return (g.chan >= AFEC1_START ? g.chan - AFEC1_START : g.chan);
}

#define ADC_FREQ_MAX 6000000UL
DECL_CONSTANT("ADC_MAX", 4095);

static int
init_afec(Afec* afec) {

    // Enable PMC
    enable_pclock(afec == AFEC0 ? ID_AFEC0 : ID_AFEC1);

    // If busy, return busy
    if ((afec->AFE_ISR & AFE_ISR_DRDY) == AFE_ISR_DRDY) {
        return -1;
    }

    // Reset
    afec->AFE_CR = AFE_CR_SWRST;

    // Configure afec
    uint32_t pclk = get_pclock_frequency(afec == AFEC0 ? ID_AFEC0 : ID_AFEC1);
    afec->AFE_MR = CFG_AFE_MR;
    afec->AFE_EMR = AFE_EMR_TAG | \
                     AFE_EMR_RES_NO_AVERAGE | \
                     AFE_EMR_STM;
    afec->AFE_ACR = CFG_AFE_ACR;

    // Disable interrupts
    afec->AFE_IDR = CFG_AFE_IDR;

    // Disable SW triggering
    uint32_t mr = afec->AFE_MR;

    mr &= ~(AFE_MR_TRGSEL_Msk | AFE_MR_TRGEN | AFE_MR_FREERUN_ON);
    mr |= AFE_MR_TRGEN_DIS;
    afec->AFE_MR = mr;

    return 0;
}

void
gpio_afec_init(void) {

    while(init_afec(AFEC0) != 0) {
        (void)(AFEC0->AFE_LCDR & AFE_LCDR_LDATA_Msk);
    }
    while(init_afec(AFEC1) != 0) {
        (void)(AFEC1->AFE_LCDR & AFE_LCDR_LDATA_Msk);
    }

}
DECL_INIT(gpio_afec_init);

struct gpio_adc
gpio_adc_setup(uint8_t pin)
{
    struct gpio_adc adc_pin = pin_to_gpio_adc(pin);
    Afec *afec = gpio_adc_to_afec(adc_pin);
    uint32_t afec_chan = gpio_adc_to_afec_chan(adc_pin);

    //config channel
    uint32_t reg = afec->AFE_DIFFR;
    reg &= ~(1u << afec_chan);
    afec->AFE_DIFFR = reg;
    reg = afec->AFE_CGR;
    reg &= ~(0x03u << (2 * afec_chan));
    afec->AFE_CGR = reg;

    // Configure channel
    // afec_ch_get_config_defaults(&ch_cfg);
    // afec_ch_set_config(afec, afec_chan, &ch_cfg);
    // Remove default internal offset from channel
    // See Atmel Appnote AT03078 Section 1.5 for SAM4E,
    // datasheet section 52.6.11 for SAME70
    afec->AFE_CSELR = afec_chan;
    afec->AFE_COCR = CFG_AFE_COCR;

    // Enable and calibrate Channel
    afec->AFE_CHER = 1 << afec_chan;

    #if CONFIG_MACH_SAM4E
    reg = afec->AFE_CHSR;
    afec->AFE_CDOR = reg;
    afec->AFE_CR = AFE_CR_AUTOCAL;
    #endif

    return adc_pin;
}

enum { AFE_DUMMY=0xff };
uint8_t active_channel = AFE_DUMMY;

// Try to sample a value. Returns zero if sample ready, otherwise
// returns the number of clock ticks the caller should wait before
// retrying this function.
uint32_t
gpio_adc_sample(struct gpio_adc g)
{
    Afec *afec = gpio_adc_to_afec(g);
    uint32_t afec_chan = gpio_adc_to_afec_chan(g);
    if (active_channel == g.chan) {
        if ((afec->AFE_ISR & AFE_ISR_DRDY)
            && (afec->AFE_ISR & (1 << afec_chan))) {
            // Sample now ready
            return 0;
        } else {
            // Busy
            goto need_delay;
        }
    } else if (active_channel != AFE_DUMMY) {
        goto need_delay;
    }

    afec->AFE_CHDR = 0x803F; // Disable all channels
    afec->AFE_CHER = 1 << afec_chan;

    active_channel = g.chan;

    for (uint32_t chan = 0; chan < 16; ++chan)
    {
        if ((afec->AFE_ISR & (1 << chan)) != 0)
        {
            afec->AFE_CSELR = chan;
            (void)(afec->AFE_CDR);
        }
    }
    afec->AFE_CR = AFE_CR_START;

need_delay:
    // about 400 mcu clock cycles or 40 afec cycles
    return ADC_FREQ_MAX * 10000ULL / CONFIG_CLOCK_FREQ;
}

// Read a value; use only after gpio_adc_sample() returns zero
uint16_t
gpio_adc_read(struct gpio_adc g)
{
    Afec *afec = gpio_adc_to_afec(g);
    uint32_t afec_chan = gpio_adc_to_afec_chan(g);
    active_channel = AFE_DUMMY;
    afec->AFE_CSELR = afec_chan;
    return afec->AFE_CDR;
}

// Cancel a sample that may have been started with gpio_adc_sample()
void
gpio_adc_cancel_sample(struct gpio_adc g)
{
    if (active_channel == g.chan) {
        active_channel = AFE_DUMMY;
    }
}