aboutsummaryrefslogtreecommitdiff
path: root/contrib/adc-mkl27z.c
blob: 35f3e0b0a3f6b2c8a30f9877f4aa1d1b95ad11a5 (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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*
 * adc-mkl27z.c - ADC driver for MKL27Z
 *               In this ADC driver, there are NeuG specific parts.
 *               It only records lower 8-bit of 16-bit data.
 *               You need to modify to use this as generic ADC driver.
 *
 * Copyright (C) 2016  Flying Stone Technology
 * Author: NIIBE Yutaka <gniibe@fsij.org>
 *
 * This file is a part of Chopstx, a thread library for embedded.
 *
 * Chopstx is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chopstx is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * As additional permission under GNU GPL version 3 section 7, you may
 * distribute non-source form of the Program without the copy of the
 * GNU GPL normally required by section 4, provided you inform the
 * receipents of GNU GPL by a written offer.
 *
 */

#include <stdint.h>
#include <stdlib.h>
#include <chopstx.h>
#include <mcu/mkl27z.h>

struct DMAMUX {
  volatile uint32_t CHCFG0;
  volatile uint32_t CHCFG1;
  volatile uint32_t CHCFG2;
  volatile uint32_t CHCFG3;
};
static struct DMAMUX *const DMAMUX = (struct DMAMUX *)0x40021000;

#define INTR_REQ_DMA0 0

struct DMA {
  volatile uint32_t SAR;
  volatile uint32_t DAR;
  volatile uint32_t DSR_BCR;
  volatile uint32_t DCR;
};
static struct DMA *const DMA0 = (struct DMA *)0x40008100;
static struct DMA *const DMA1 = (struct DMA *)0x40008110;


/* We don't use ADC interrupt.  Just for reference.  */
#define INTR_REQ_ADC 15

struct ADC {
  volatile uint32_t SC1[2];/* Status and Control Registers 1 */
  volatile uint32_t CFG1;  /* Configuration Register 1       */
  volatile uint32_t CFG2;  /* Configuration Register 2       */
  volatile uint32_t R[2];  /* Data Result Register           */

  /* Compare Value Registers 1, 2 */
  volatile uint32_t CV1;
  volatile uint32_t CV2;

  volatile uint32_t SC2;   /* Status and Control Register 2  */
  volatile uint32_t SC3;   /* Status and Control Register 3  */

  volatile uint32_t OFS;   /* Offset Correction Register     */
  volatile uint32_t PG;    /* Plus-Side Gain Register        */
  volatile uint32_t MG;    /* Minus-Side Gain Register       */

  /* Plus-Side General Calibration Value Registers */
  volatile uint32_t CLPD;
  volatile uint32_t CLPS;
  volatile uint32_t CLP4;
  volatile uint32_t CLP3;
  volatile uint32_t CLP2;
  volatile uint32_t CLP1;
  volatile uint32_t CLP0;
  uint32_t rsvd0;
  /* Minus-Side General Calibration Value Registers */
  volatile uint32_t CLMD;
  volatile uint32_t CLMS;
  volatile uint32_t CLM4;
  volatile uint32_t CLM3;
  volatile uint32_t CLM2;
  volatile uint32_t CLM1;
  volatile uint32_t CLM0;
};
static struct ADC *const ADC0 = (struct ADC *)0x4003B000;

/* SC1 */
#define ADC_SC1_DIFF            (1 << 5)
#define ADC_SC1_AIEN            (1 << 6)
#define ADC_SC1_COCO            (1 << 7)
#define ADC_SC1_TEMPSENSOR      26
#define ADC_SC1_BANDGAP         27
#define ADC_SC1_ADCSTOP         31

/* CFG1 */
#define ADC_CLOCK_SOURCE_ASYNCH (3 << 0)
#define ADC_MODE_16BIT          (3 << 2)
#define ADC_ADLSMP_SHORT        (0 << 4)
#define ADC_ADLSMP_LONG         (1 << 4)
#define ADC_ADIV_1              (0 << 5)
#define ADC_ADIV_8              (3 << 5)
#define ADC_ADLPC_NORMAL        (0 << 7)
#define ADC_ADLPC_LOWPOWER      (1 << 7)
/**/
#define ADC_CLOCK_SOURCE ADC_CLOCK_SOURCE_ASYNCH
#define ADC_MODE         ADC_MODE_16BIT
#define ADC_ADLSMP       ADC_ADLSMP_SHORT
#define ADC_ADIV         ADC_ADIV_1
#define ADC_ADLPC        ADC_ADLPC_LOWPOWER

/* CFG2 */
#define ADC_ADLSTS_DEFAULT      0 /* 24 cycles if CFG1.ADLSMP=1, 4 if not.  */
#define ADC_ADHSC_NORMAL        (0 << 2)
#define ADC_ADHSC_HIGHSPEED     (1 << 2)
#define ADC_ADACK_DISABLE       (0 << 3)
#define ADC_ADACK_ENABLE        (1 << 3)
#define ADC_MUXSEL_A            (0 << 4)
#define ADC_MUXSEL_B            (1 << 4)
/**/
#define ADC_ADLSTS       ADC_ADLSTS_DEFAULT 
#define ADC_ADHSC        ADC_ADHSC_NORMAL
#define ADC_ADACKEN      ADC_ADACK_ENABLE
#define ADC_MUXSEL       ADC_MUXSEL_A

/* SC2 */
#define ADC_SC2_REFSEL_DEFAULT  1 /* Internal Voltage Reference??? */
#define ADC_SC2_DMAEN           (1 << 2)
#define ADC_SC2_ACREN           (1 << 3)
#define ADC_SC2_ACFGT           (1 << 4)
#define ADC_SC2_ACFE            (1 << 5)
#define ADC_SC2_ADTRG		(1 << 6) /* For hardware trigger */

/* SC3 */
#define ADC_SC3_AVGS11          0x03
#define ADC_SC3_AVGE            (1 << 2)
#define ADC_SC3_ADCO            (1 << 3)
#define ADC_SC3_CALF            (1 << 6)
#define ADC_SC3_CAL             (1 << 7)

#define ADC_DMA_SLOT_NUM 40

/*
 * Buffer to save ADC data.
 */
uint32_t adc_buf[64];

static const uint32_t adc0_sc1_setting = ADC_SC1_TEMPSENSOR;

static chopstx_intr_t adc_intr;

struct adc_internal {
  uint32_t buf[64];
  uint8_t *p;
  int phase : 8;
  int count : 8;
};
struct adc_internal adc;

/*
 * Initialize ADC module, do calibration.
 *
 * This is called by MAIN, only once, hopefully before creating any
 * other threads (to be accurate).
 *
 * We configure ADC0 to kick DMA0, configure DMA0 to kick DMA1.
 * DMA0 records output of ADC0 to the ADC.BUF.
 * DMA1 kicks ADC0 again to get another value.
 *
 * ADC0 --[finish conversion]--> DMA0 --[Link channel 1]--> DMA1
 */
int
adc_init (void)
{
  uint32_t v;

  /* Enable ADC0 and DMAMUX clock.  */
  SIM->SCGC6 |= (1 << 27) | (1 << 1);
  /* Enable DMA clock.  */
  SIM->SCGC7 |= (1 << 8);

  /* ADC0 setting for calibration.  */
  ADC0->CFG1 = ADC_CLOCK_SOURCE | ADC_MODE | ADC_ADLSMP | ADC_ADIV | ADC_ADLPC;
  ADC0->CFG2 = ADC_ADLSTS | ADC_ADHSC | ADC_ADACKEN | ADC_MUXSEL;
  ADC0->SC2 = ADC_SC2_REFSEL_DEFAULT;
  ADC0->SC3 = ADC_SC3_CAL | ADC_SC3_CALF | ADC_SC3_AVGE | ADC_SC3_AVGS11;

  /* Wait ADC completion */
  while ((ADC0->SC1[0] & ADC_SC1_COCO) == 0)
    if ((ADC0->SC3 & ADC_SC3_CALF) != 0)
      /* Calibration failure */
      return -1;

  if ((ADC0->SC3 & ADC_SC3_CALF) != 0)
    /* Calibration failure */
    return -1;

  /* Configure PG by the calibration values.  */
  v = ADC0->CLP0 + ADC0->CLP1 + ADC0->CLP2 + ADC0->CLP3 + ADC0->CLP4 + ADC0->CLPS;
  ADC0->PG = 0x8000 | (v >> 1);

  /* Configure MG by the calibration values.  */
  v = ADC0->CLM0 + ADC0->CLM1 + ADC0->CLM2 + ADC0->CLM3 + ADC0->CLM4 + ADC0->CLMS;
  ADC0->MG = 0x8000 | (v >> 1);

  ADC0->SC1[0] = ADC_SC1_ADCSTOP;

  /* DMAMUX setting.  */
  DMAMUX->CHCFG0 = (1 << 7) | ADC_DMA_SLOT_NUM;

  /* DMA0 initial setting.  */
  DMA0->SAR = (uint32_t)&ADC0->R[0];

  /* DMA1 initial setting.  */
  DMA1->SAR = (uint32_t)&adc0_sc1_setting;
  DMA1->DAR = (uint32_t)&ADC0->SC1[0];
  
  chopstx_claim_irq (&adc_intr, INTR_REQ_DMA0);
  return 0;
}

/*
 * Start using ADC.
 */
void
adc_start (void)
{
  ADC0->CFG1 = ADC_CLOCK_SOURCE | ADC_MODE | ADC_ADLSMP | ADC_ADIV | ADC_ADLPC;
  ADC0->CFG2 = ADC_ADLSTS | ADC_ADHSC | ADC_ADACKEN | ADC_MUXSEL;
  ADC0->SC2 = ADC_SC2_REFSEL_DEFAULT | ADC_SC2_DMAEN;
  ADC0->SC3 = 0;
}

/*
 * Kick getting data for COUNT times.
 * Data will be saved in ADC_BUF starting at OFFSET.
 */
static void
adc_start_conversion_internal (int count)
{
  /* DMA0 setting.  */
  DMA0->DAR = (uint32_t)&adc.buf[0];
  DMA0->DSR_BCR = 4 * count;
  DMA0->DCR = (1 << 31) | (1 << 30) | (1 << 29) | (0 << 20) | (1 << 19)
            | (0 << 17) | (1 <<  7) | (2 <<  4) | (1 <<  2);

  /* Kick DMA1.  */
  DMA1->DSR_BCR = 4 * count;
  DMA1->DCR = (1 << 30) | (1 << 29) | (0 << 19) | (0 << 17) | (1 << 16) | (1 << 7);
}


/*
 * Kick getting data for COUNT times.
 * Data will be saved in ADC_BUF starting at OFFSET.
 */
void
adc_start_conversion (int offset, int count)
{
  adc.p = (uint8_t *)&adc_buf[offset];
  adc.phase = 0;
  adc.count = count;
  adc_start_conversion_internal (count);
}


static void
adc_stop_conversion (void)
{
  ADC0->SC1[0] = ADC_SC1_ADCSTOP;
}

/*
 * Stop using ADC.
 */
void
adc_stop (void)
{
  SIM->SCGC6 &= ~(1 << 27);
}

/*
 * Return 0 on success.
 * Return 1 on error.
 */
int
adc_wait_completion (void)
{
  struct chx_poll_head *pd_array[1] = { (struct chx_poll_head *)&adc_intr };
  int i;

  while (1)
    {
      /* Wait DMA completion */
      chopstx_poll (NULL, 1, pd_array);

      DMA0->DSR_BCR = (1 << 24);
      DMA1->DSR_BCR = (1 << 24);

      adc_stop_conversion ();

      for (i = 0; i < adc.count; i++)
	*adc.p++ = (uint8_t)adc.buf[i];

      if (++adc.phase >= 4)
	break;

      adc_start_conversion_internal (adc.count);
    }

  return 0;
}