--- /dev/null
+/*
+ * Copyright © 2020 Keith Packard <keithp@keithp.com>
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <snek.h>
+#include <ao.h>
+#include <ao_dac-samd21.h>
+#include <ao_tcc-samd21.h>
+
+/* Max DAC output value. We're using left-adjusted values */
+#define SNEK_DAC_MAX 65535
+
+#ifdef SNEK_SAMD21_DAC_TIMER
+/*
+ * If there's a timer available, we can use that
+ * to implement the 'tone' function
+ */
+
+#include "sine.h"
+
+#define NSINE (sizeof(sine) / sizeof(sine[0]))
+
+static uint16_t current_power;
+static uint16_t power;
+static uint32_t phase;
+static uint32_t phase_step;
+static volatile bool dac_running;
+
+#define _paste2(x,y) x ## y
+#define _paste3(x,y,z) x ## y ## z
+#define paste2(x,y) _paste2(x,y)
+#define paste3(x,y,z) _paste3(x,y,z)
+#define SAMD21_TCC paste2(samd21_tcc, SNEK_SAMD21_DAC_TIMER)
+#define SAMD21_TCC_ISR paste3(samd21_tcc, SNEK_SAMD21_DAC_TIMER, _isr)
+
+#define AO_DAC_RATE 24000
+
+#define UINT_TO_FIXED(u) ((uint32_t) (u) << 16)
+#define FIXED_TO_UINT(u) ((u) >> 16)
+
+void
+SAMD21_TCC_ISR(void)
+{
+ uint32_t intflag = SAMD21_TCC.intflag;
+ SAMD21_TCC.intflag = intflag;
+ if (intflag & (1 << SAMD21_TCC_INTFLAG_OVF)) {
+ if (phase_step) {
+ samd21_dac.data = ((uint32_t) sine[FIXED_TO_UINT(phase)] * current_power) >> 16;
+ if ((phase += phase_step) >= UINT_TO_FIXED(NSINE)) {
+ phase -= UINT_TO_FIXED(NSINE);
+
+ current_power = power;
+
+ /* Stop output at zero crossing when no longer outputing tone */
+ if (!dac_running) {
+ phase_step = 0;
+ phase = 0;
+ SAMD21_TCC.intenclr = (1 << SAMD21_TCC_INTFLAG_OVF);
+ }
+ }
+ }
+ }
+}
+
+void
+ao_dac_set_hz(float hz)
+{
+ /* samples/second = AC_DAC_RATE
+ *
+ * cycles/second = hz
+ *
+ * samples/cycle = AC_DAC_RATE / hz
+ *
+ * step/cycle = 256
+ *
+ * step/sample = step/cycle * cycle/samples
+ * = TWO_PI * hz / AC_DAC_RATE;
+ */
+ uint32_t new_phase_step = (float) UINT_TO_FIXED(NSINE) * hz / (float) AO_DAC_RATE;
+ ao_arch_critical(
+ if (new_phase_step) {
+ dac_running = true;
+ phase_step = new_phase_step;
+ SAMD21_TCC.intenset = (1 << SAMD21_TCC_INTFLAG_OVF);
+ } else {
+ dac_running = false;
+ });
+}
+
+static void
+ao_dac_timer_init(void)
+{
+ /* Adjust timer to interrupt once per sample period */
+ SAMD21_TCC.per = AO_HCLK / AO_DAC_RATE;
+
+ /* Enable timer interrupts */
+ samd21_nvic_set_enable(paste3(SAMD21_NVIC_ISR_TCC, SNEK_SAMD21_DAC_TIMER, _POS));
+ samd21_nvic_set_priority(paste3(SAMD21_NVIC_ISR_TCC, SNEK_SAMD21_DAC_TIMER, _POS), 3);
+}
+#else
+#define ao_dac_timer_init()
+#endif
+
+static void
+ao_dac_sync(void)
+{
+ while (samd21_dac.status & (1 << SAMD21_DAC_STATUS_SYNCBUSY))
+ ;
+}
+
+void
+ao_dac_set(uint16_t new_power)
+{
+#if SNEK_DAC_MAX != SNEK_PWM_MAX
+ new_power = (uint16_t) ((uint32_t) new_power * SNEK_DAC_MAX) / SNEK_PWM_MAX;
+#endif
+
+ ao_arch_critical(
+#ifdef SNEK_SAMD21_DAC_TIMER
+ power = new_power;
+ /*
+ * When not generating a tone, just set the DAC
+ * output to the requested level
+ */
+ if (!phase_step) {
+ current_power = new_power;
+ samd21_dac.data = new_power;
+ }
+#else
+ samd21_dac.data = new_power;
+#endif
+ );
+}
+
+void
+ao_dac_init(void)
+{
+ /* supply a clock */
+ samd21_gclk_clkctrl(0, SAMD21_GCLK_CLKCTRL_ID_DAC);
+
+ /* enable the device */
+ samd21_pm.apbcmask |= (1 << SAMD21_PM_APBCMASK_DAC);
+
+ /* reset */
+ samd21_dac.ctrla = (1 << SAMD21_DAC_CTRLA_SWRST);
+
+ while ((samd21_dac.ctrla & (1 << SAMD21_DAC_CTRLA_SWRST)) != 0 ||
+ (samd21_dac.status & (1 << SAMD21_DAC_STATUS_SYNCBUSY)) != 0)
+ ao_arch_nop();
+
+ /* Configure using VDD as reference */
+ samd21_dac.ctrlb = ((1 << SAMD21_DAC_CTRLB_EOEN) |
+ (0 << SAMD21_DAC_CTRLB_IOEN) |
+ (1 << SAMD21_DAC_CTRLB_LEFTADJ) |
+ (0 << SAMD21_DAC_CTRLB_VPD) |
+ (1 << SAMD21_DAC_CTRLB_BDWP) |
+ (SAMD21_DAC_CTRLB_REFSEL_VDDANA << SAMD21_DAC_CTRLB_REFSEL));
+
+ ao_dac_sync();
+
+ samd21_dac.ctrla = (1 << SAMD21_DAC_CTRLA_ENABLE);
+
+ ao_dac_sync();
+
+ ao_dac_timer_init();
+}