| /* |
| * Copyright (C) 2015 Google Inc. |
| * |
| * 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; version 2 of the License. |
| * |
| * 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. |
| */ |
| |
| #include <assert.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| |
| #include "base/container_of.h" |
| #include "base/xalloc.h" |
| #include "drivers/sound/gpio_pdm.h" |
| |
| /* Square wave with 1 channel */ |
| static void pdm_square_wave(uint16_t *data, unsigned samples, |
| uint32_t freq, uint16_t volume) |
| { |
| unsigned period = samples / freq; |
| unsigned half = period / 2; |
| |
| while (samples) { |
| for (int i = 0; samples && i < half; samples--, i++) |
| *data++ = volume; |
| for (int i = 0; samples && i < period - half; samples--, i++) |
| *data++ = -volume; |
| } |
| } |
| |
| /* Send PDM data bit, left and right channels */ |
| static void gpio_pdm_send_bit(GpioPdm *pdm, int bit) |
| { |
| switch (pdm->channels) { |
| case 1: |
| /* Mono: send data on both clock edges */ |
| gpio_set(pdm->data_gpio, bit); |
| |
| /* Toggle Clock */ |
| gpio_set(pdm->clock_gpio, gpio_get(pdm->clock_gpio) ^ 1); |
| break; |
| |
| case 2: |
| /* Stereo: left channel on rising edge */ |
| gpio_set(pdm->data_gpio, bit); |
| gpio_set(pdm->clock_gpio, 1); |
| |
| /* Stereo: right channel on falling edge */ |
| gpio_set(pdm->data_gpio, bit); |
| gpio_set(pdm->clock_gpio, 0); |
| break; |
| |
| default: |
| printf("%s: Unsupported channel count %d\n", |
| __func__, pdm->channels); |
| } |
| } |
| |
| /* Start PDM clock with no data */ |
| static void gpio_pdm_clock_start(GpioPdm *pdm) |
| { |
| for (int i = 0; i < pdm->clock_start; i++) |
| gpio_pdm_send_bit(pdm, 0); |
| } |
| |
| /* Send PDM data */ |
| static void gpio_pdm_send(GpioPdm *pdm, uint16_t *data, size_t length) |
| { |
| for (; length > 0; length--, data++) |
| for (int i = 0; i < 16; i++) |
| gpio_pdm_send_bit(pdm, !!(*data & (1 << i))); |
| } |
| |
| static int gpio_pdm_play(SoundOps *me, uint32_t msec, uint32_t frequency) |
| { |
| GpioPdm *pdm = container_of(me, GpioPdm, ops); |
| |
| /* Prepare a buffer for 1 second of sound. */ |
| unsigned bytes = pdm->sample_rate * sizeof(uint16_t); |
| uint16_t *data = xmalloc(bytes); |
| |
| /* Generate a square wave tone with one channel */ |
| pdm_square_wave(data, pdm->sample_rate, frequency, pdm->volume); |
| |
| /* Start the PDM clock */ |
| gpio_pdm_clock_start(pdm); |
| |
| /* Send 1 second chunks */ |
| while (msec >= 1000) { |
| gpio_pdm_send(pdm, data, bytes / sizeof(uint16_t)); |
| msec -= 1000; |
| } |
| |
| /* Send remaining portion of a second */ |
| if (msec) { |
| size_t len = (bytes * msec) / (sizeof(uint16_t) * 1000); |
| gpio_pdm_send(pdm, data, len); |
| } |
| |
| free(data); |
| return 0; |
| } |
| |
| GpioPdm *new_gpio_pdm(GpioOps *clock_gpio, GpioOps *data_gpio, |
| unsigned clock_start, unsigned sample_rate, |
| unsigned channels, uint16_t volume) |
| { |
| GpioPdm *pdm = xzalloc(sizeof(*pdm)); |
| |
| pdm->ops.play = &gpio_pdm_play; |
| |
| pdm->clock_gpio = clock_gpio; |
| pdm->data_gpio = data_gpio; |
| |
| pdm->clock_start = clock_start; |
| pdm->sample_rate = sample_rate; |
| pdm->channels = channels; |
| pdm->volume = volume; |
| |
| return pdm; |
| } |