aboutsummaryrefslogtreecommitdiffstats
path: root/src/sam3/sam4e_afec.c
blob: 69205c50122d788f52cc2efb6d4e0ec7700aa85a (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
// 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
#include "sam4e.h" // AFEC0
#include "sched.h" // sched_shutdown

static const uint8_t afec_pins[] = {
    //remove first channel, since it offsets the channel number: GPIO('A', 8),
    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),
    // 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),
};

#define AFEC1_START 15 // The first 15 pins are on afec0

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
    if (afec == AFEC0)
        PMC->PMC_PCER0 = 1 << ID_AFEC0;
    else
        PMC->PMC_PCER0 = 1 << 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
    afec->AFE_MR = AFE_MR_ANACH_ALLOWED | \
                    AFE_MR_PRESCAL (SystemCoreClock / (2 * ADC_FREQ_MAX) -1) | \
                    AFE_MR_SETTLING_AST3 | \
                    AFE_MR_TRACKTIM(2) | \
                    AFE_MR_TRANSFER(1) | \
                    AFE_MR_STARTUP_SUT64;
    afec->AFE_EMR = AFE_EMR_TAG | \
                     AFE_EMR_RES_NO_AVERAGE | \
                     AFE_EMR_STM;
    afec->AFE_ACR = AFE_ACR_IBCTL(1);

    // Disable interrupts
    afec->AFE_IDR = 0xDF00803F;

    // 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));
    reg |= 1 << (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
    afec->AFE_CSELR = afec_chan;
    afec->AFE_COCR = (0x800 & AFE_COCR_AOFF_Msk);

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

    reg = afec->AFE_CHSR;
    afec->AFE_CDOR = reg;
    afec->AFE_CR = AFE_CR_AUTOCAL;

    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:
    return ADC_FREQ_MAX * 10000ULL / CONFIG_CLOCK_FREQ; // about 400 mcu clock cycles or 40 afec cycles
}

// 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;
    }
}