blob: 2992bbb6ff6728ffba8ad15a68988ddb77bdc831 [file] [log] [blame]
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <assert.h>
#include <libpayload.h>
#include <stdint.h>
#include "base/cleanup_funcs.h"
#include "base/container_of.h"
#include "drivers/gpio/gpio.h"
#include "drivers/sound/ipq806x.h"
#include "drivers/sound/ipq806x-reg.h"
#include "drivers/sound/sound.h"
typedef struct __attribute__((packed)) {
uint32_t control;
} Ipq806xI2sCtrlRegs;
typedef struct __attribute__((packed)) {
uint32_t control;
uint32_t base_address;
uint32_t buffer_length;
uint32_t UNUSED;
uint32_t period_length;
} Ipq806xI2sDmaRegs;
typedef struct __attribute__((packed)) {
uint32_t ns;
uint32_t UNUSED;
uint32_t status;
} Ipq806xLccMi2sRegs;
static size_t ipq806x_sound_make_tone(int16_t *buffer, size_t buffer_length,
unsigned int channels, unsigned int frame_rate,
unsigned int bitwidth, uint32_t frequency,
uint16_t volume)
{
const unsigned int period = frame_rate / frequency;
const unsigned int half = period / 2;
const unsigned int frame_size = channels * (bitwidth / 8);
const unsigned int frames = buffer_length / frame_size;
unsigned int frames_left = frames;
unsigned int i, j;
while (frames_left >= period) {
for (i = 0; i < half; frames_left--, i++)
for (j = 0; j < channels; j++)
*buffer++ = volume;
for (i = 0; i < period - half; frames_left--, i++)
for (j = 0; j < channels; j++)
*buffer++ = -volume;
}
return (frames - frames_left) * frame_size;
}
static int ipq806x_sound_init(Ipq806xSound *sound)
{
Ipq806xI2sCtrlRegs *ctrl_regs = sound->ctrl_regs;
Ipq806xI2sDmaRegs *dma_regs = sound->dma_regs;
GpioOps *gpio = sound->gpio;
const unsigned int bitwidth = sound->bitwidth;
const unsigned int channels = sound->channels;
uint32_t regval;
regval = 0;
switch (channels) {
case 1:
regval |= LPAIF_MI2SCTL_SPKMODE_SD0;
regval |= LPAIF_MI2SCTL_SPKMONO_MONO;
break;
case 2:
regval |= LPAIF_MI2SCTL_SPKMODE_SD0;
regval |= LPAIF_MI2SCTL_SPKMONO_STEREO;
break;
case 4:
regval |= LPAIF_MI2SCTL_SPKMODE_QUAD01;
regval |= LPAIF_MI2SCTL_SPKMONO_STEREO;
break;
case 6:
regval |= LPAIF_MI2SCTL_SPKMODE_6CH;
regval |= LPAIF_MI2SCTL_SPKMONO_STEREO;
break;
case 8:
regval |= LPAIF_MI2SCTL_SPKMODE_8CH;
regval |= LPAIF_MI2SCTL_SPKMONO_STEREO;
break;
default:
printf("%s: invalid channels given: %u\n", __func__, channels);
return 1;
}
switch (bitwidth) {
case 16:
regval |= LPAIF_MI2SCTL_BITWIDTH_16;
break;
case 24:
regval |= LPAIF_MI2SCTL_BITWIDTH_24;
break;
case 32:
regval |= LPAIF_MI2SCTL_BITWIDTH_32;
break;
default:
printf("%s: invalid bitwidth given: %u\n", __func__, bitwidth);
return 1;
}
writel(regval, &ctrl_regs->control);
regval = 0;
regval |= LPAIF_DMACTL_BURST_EN;
regval |= LPAIF_DMACTL_AUDIO_INTF_MI2S;
regval |= LPAIF_DMACTL_FIFO_WM_8;
switch (bitwidth) {
case 16:
switch (channels) {
case 1:
case 2:
regval |= LPAIF_DMACTL_WPSCNT_SINGLE;
break;
case 4:
regval |= LPAIF_DMACTL_WPSCNT_DOUBLE;
break;
case 6:
regval |= LPAIF_DMACTL_WPSCNT_TRIPLE;
break;
case 8:
regval |= LPAIF_DMACTL_WPSCNT_QUAD;
break;
default:
printf("%s: invalid PCM config given: bw=%u, ch=%u\n",
__func__, bitwidth, channels);
return 1;
}
break;
case 24:
case 32:
switch (channels) {
case 1:
regval |= LPAIF_DMACTL_WPSCNT_SINGLE;
break;
case 2:
regval |= LPAIF_DMACTL_WPSCNT_DOUBLE;
break;
case 4:
regval |= LPAIF_DMACTL_WPSCNT_QUAD;
break;
case 6:
regval |= LPAIF_DMACTL_WPSCNT_SIXPACK;
break;
case 8:
regval |= LPAIF_DMACTL_WPSCNT_OCTAL;
break;
default:
printf("%s: invalid PCM config given: bw=%u, ch=%u\n",
__func__, bitwidth, channels);
return 1;
}
break;
default:
printf("%s: invalid PCM config given: bw=%d, ch=%d\n",
__func__, bitwidth, channels);
return 1;
}
writel(regval, &dma_regs->control);
/* Initialize the GPIOs required for the board */
board_dac_gpio_config();
gpio->set(gpio, 1);
board_i2s_gpio_config();
return 0;
}
static int ipq806x_sound_start(SoundOps *me, uint32_t frequency)
{
Ipq806xSound *sound = container_of(me, Ipq806xSound, ops);
Ipq806xI2sCtrlRegs *ctrl_regs = sound->ctrl_regs;
Ipq806xI2sDmaRegs *dma_regs = sound->dma_regs;
GpioOps *gpio = sound->gpio;
uint32_t buffer_val, length_val, regval;
size_t audio_length;
assert(frequency);
if (!sound->initialized) {
if (ipq806x_sound_init(sound))
return 1;
sound->initialized = 1;
}
audio_length = ipq806x_sound_make_tone(sound->buffer,
sound->buffer_length, sound->channels,
sound->frame_rate, sound->bitwidth,
frequency, sound->volume);
buffer_val = (uint32_t)sound->buffer;
length_val = (audio_length & 0xFFFFFFF0) >> 2;
writel(buffer_val, &dma_regs->base_address);
writel(length_val - 1, &dma_regs->buffer_length);
writel(length_val + 1, &dma_regs->period_length);
regval = readl(&dma_regs->control);
regval |= LPAIF_DMACTL_ENABLE;
writel(regval, &dma_regs->control);
regval = readl(&ctrl_regs->control);
regval |= LPAIF_MI2SCTL_SPKEN;
writel(regval, &ctrl_regs->control);
mdelay(2);
gpio->set(gpio, 1);
return 0;
}
static int ipq806x_sound_stop(SoundOps *me)
{
Ipq806xSound *sound = container_of(me, Ipq806xSound, ops);
Ipq806xI2sCtrlRegs *ctrl_regs = sound->ctrl_regs;
Ipq806xI2sDmaRegs *dma_regs = sound->dma_regs;
GpioOps *gpio = sound->gpio;
uint32_t regval;
if (!sound->initialized)
return 0;
gpio->set(gpio, 0);
mdelay(1);
regval = readl(&ctrl_regs->control);
regval &= ~LPAIF_MI2SCTL_SPKEN;
writel(regval, &ctrl_regs->control);
regval = readl(&dma_regs->control);
regval &= ~LPAIF_DMACTL_ENABLE;
writel(regval, &dma_regs->control);
return 0;
}
static int ipq806x_sound_play(SoundOps *me, uint32_t msec, uint32_t frequency)
{
int ret;
ret = ipq806x_sound_start(me, frequency);
if (ret)
return ret;
mdelay(msec);
ret = ipq806x_sound_stop(me);
return ret;
}
static int ipq806x_set_volume(SoundOps *me, uint32_t volume)
{
Ipq806xSound *sound = container_of(me, Ipq806xSound, ops);
if (volume > 100)
volume = 100; /* Just in case. */
/* Max IPQ volume setting is 16000. */
sound->volume = 160 * volume;
return 0;
}
static int ipq806x_sound_shutdown(struct CleanupFunc *cleanup, CleanupType type)
{
Ipq806xSound *sound = (Ipq806xSound *)cleanup->data;
Ipq806xLccMi2sRegs *mi2s_regs = sound->lcc_mi2s_regs;
uint32_t regval;
printf("Shutting off the MI2S audio clock.\n");
regval = readl(&mi2s_regs->ns);
regval &= ~LCC_MI2S_NS_OSR_CXC_ENABLE;
regval &= ~LCC_MI2S_NS_BIT_CXC_ENABLE;
writel(regval, &mi2s_regs->ns);
udelay(10);
regval = readl(&mi2s_regs->status);
if (!(regval & LCC_MI2S_STAT_OSR_CLK_MASK))
if (!(regval & LCC_MI2S_STAT_BIT_CLK_MASK))
return 0;
printf("%s: error disabling MI2S clocks: %u\n", __func__, regval);
return 1;
}
Ipq806xSound *new_ipq806x_sound(GpioOps *gpio, unsigned int frame_rate,
unsigned int channels, unsigned int bitwidth, uint16_t volume)
{
Ipq806xSound *sound = xzalloc(sizeof(*sound));
CleanupFunc *cleanup = xzalloc(sizeof(*cleanup));
assert(gpio != NULL);
sound->ops.start = &ipq806x_sound_start;
sound->ops.stop = &ipq806x_sound_stop;
sound->ops.play = &ipq806x_sound_play;
sound->ops.set_volume = &ipq806x_set_volume;
sound->gpio = gpio;
sound->ctrl_regs = (void *)(IPQ806X_LPAIF_BASE +
LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S));
sound->dma_regs = (void *)(IPQ806X_LPAIF_BASE +
LPAIF_DMA_ADDR(LPAIF_DMA_RD_CH_MI2S, 0x00));
sound->lcc_mi2s_regs = (void *)(IPQ806X_LCC_BASE + LCC_MI2S_NS_REG);
sound->buffer = (void *)(IPQ806X_LPM_BASE);
sound->buffer_length = LPM_SIZE;
sound->frame_rate = frame_rate;
sound->channels = channels;
sound->bitwidth = bitwidth;
sound->volume = volume;
cleanup->cleanup = &ipq806x_sound_shutdown;
cleanup->types = CleanupOnHandoff | CleanupOnLegacy;
cleanup->data = sound;
list_insert_after(&cleanup->list_node, &cleanup_funcs);
return sound;
}