blob: 3688c8ed73effb920393f6342c40647e763b7b15 [file] [log] [blame]
/*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
* Copyright 2013 Google Inc. All rights reserved.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <libpayload.h>
#include "base/container_of.h"
#include "drivers/sound/tegra_ahub.h"
/*
* Each TX CIF transmits data into the XBAR. Each RX CIF can receive audio
* transmitted by a particular TX CIF.
*/
typedef struct TegraXbarRegs {
uint32_t apbif_rx0; /* AUDIO_APBIF_RX0, offset 0x00 */
uint32_t apbif_rx1; /* AUDIO_APBIF_RX1, offset 0x04 */
uint32_t apbif_rx2; /* AUDIO_APBIF_RX2, offset 0x08 */
uint32_t apbif_rx3; /* AUDIO_APBIF_RX3, offset 0x0C */
uint32_t i2s0_rx0; /* AUDIO_I2S0_RX0, offset 0x10 */
uint32_t i2s1_rx0; /* AUDIO_I2S1_RX0, offset 0x14 */
uint32_t i2s2_rx0; /* AUDIO_I2S2_RX0, offset 0x18 */
uint32_t i2s3_rx0; /* AUDIO_I2S3_RX0, offset 0x1C */
uint32_t i2s4_rx0; /* AUDIO_I2S4_RX0, offset 0x20 */
uint32_t dam0_rx0; /* AUDIO_DAM0_RX0, offset 0x24 */
uint32_t dam0_rx1; /* AUDIO_DAM0_RX1, offset 0x28 */
uint32_t dam1_rx0; /* AUDIO_DAM1_RX0, offset 0x2C */
uint32_t dam1_rx1; /* AUDIO_DAM1_RX1, offset 0x30 */
uint32_t dam2_rx0; /* AUDIO_DAM2_RX0, offset 0x34 */
uint32_t dam2_rx1; /* AUDIO_DAM2_RX1, offset 0x38 */
uint32_t spdif_rx0; /* AUDIO_SPDIF_RX0, offset 0x3C */
uint32_t spdif_rx1; /* AUDIO_SPDIF_RX1, offset 0x40 */
uint32_t apbif_rx4; /* AUDIO_APBIF_RX4, offset 0x44 */
uint32_t apbif_rx5; /* AUDIO_APBIF_RX4, offset 0x48 */
uint32_t apbif_rx6; /* AUDIO_APBIF_RX4, offset 0x4C */
uint32_t apbif_rx7; /* AUDIO_APBIF_RX4, offset 0x50 */
uint32_t apbif_rx8; /* AUDIO_APBIF_RX4, offset 0x54 */
uint32_t apbif_rx9; /* AUDIO_APBIF_RX4, offset 0x58 */
uint32_t amx0_rx0; /* AUDIO_AMX0_RX0, offset 0x5C */
uint32_t amx0_rx1; /* AUDIO_AMX0_RX1, offset 0x60 */
uint32_t amx0_rx2; /* AUDIO_AMX0_RX2, offset 0x64 */
uint32_t amx0_rx3; /* AUDIO_AMX0_RX3, offset 0x68 */
uint32_t adx0_rx0; /* AUDIO_ADX0_RX0, offset 0x6C */
} TegraXbarRegs;
typedef struct TegraApbifRegs {
uint32_t channel0_ctrl; /* APBIF_CHANNEL0_CTRL */
uint32_t channel0_clr; /* APBIF_CHANNEL0_CLEAR */
uint32_t channel0_stat; /* APBIF_CHANNEL0_STATUS */
uint32_t channel0_txfifo; /* APBIF_CHANNEL0_TXFIFO */
uint32_t channel0_rxfifo; /* APBIF_CHANNEL0_RXFIFO */
uint32_t channel0_cif_tx0_ctrl; /* APBIF_AUDIOCIF_TX0_CTRL */
uint32_t channel0_cif_rx0_ctrl; /* APBIF_AUDIOCIF_RX0_CTRL */
uint32_t channel0_reserved0; /* RESERVED, offset 0x1C */
/* ahub_channel1_ctrl/clr/stat/txfifo/rxfifl/ciftx/cifrx ... here */
/* ahub_channel2_ctrl/clr/stat/txfifo/rxfifl/ciftx/cifrx ... here */
/* ahub_channel3_ctrl/clr/stat/txfifo/rxfifl/ciftx/cifrx ... here */
uint32_t reserved123[3*8];
uint32_t config_link_ctrl; /* APBIF_CONFIG_LINK_CTRL_0, off 0x80 */
uint32_t misc_ctrl; /* APBIF_MISC_CTRL_0, offset 0x84 */
uint32_t apbdma_live_stat; /* APBIF_APBDMA_LIVE_STATUS_0 */
uint32_t i2s_live_stat; /* APBIF_I2S_LIVE_STATUS_0 */
uint32_t dam0_live_stat; /* APBIF_DAM0_LIVE_STATUS_0 */
uint32_t dam1_live_stat; /* APBIF_DAM0_LIVE_STATUS_0 */
uint32_t dam2_live_stat; /* APBIF_DAM0_LIVE_STATUS_0 */
uint32_t spdif_live_stat; /* APBIF_SPDIF_LIVE_STATUS_0 */
uint32_t i2s_int_mask; /* APBIF_I2S_INT_MASK_0, offset 0xB0 */
uint32_t dam_int_mask; /* APBIF_DAM_INT_MASK_0 */
uint32_t reserved_int_mask; /* RESERVED, offset 0xB8 */
uint32_t spdif_int_mask; /* APBIF_SPDIF_INT_MASK_0 */
uint32_t apbif_int_mask; /* APBIF_APBIF_INT_MASK_0, off 0xC0 */
uint32_t reserved2_int_mask; /* RESERVED, offset 0xC4 */
uint32_t i2s_int_stat; /* APBIF_I2S_INT_STATUS_0, offset 0xC8 */
uint32_t dam_int_stat; /* APBIF_DAM_INT_STATUS_0 */
uint32_t reserved_int_stat; /* RESERVED, offset 0xD0 */
uint32_t spdif_int_stat; /* APBIF_SPDIF_INT_STATUS_0 */
uint32_t apbif_int_stat; /* APBIF_APBIF_INT_STATUS_0 */
uint32_t reserved2_int_stat; /* RESERVED, offset 0xDC */
uint32_t i2s_int_src; /* APBIF_I2S_INT_SOURCE_0, offset 0xE0 */
uint32_t dam_int_src; /* APBIF_DAM_INT_SOURCE_0 */
uint32_t reserved_int_src; /* RESERVED, offset 0xE8 */
uint32_t spdif_int_src; /* APBIF_SPDIF_INT_SOURCE_0 */
uint32_t apbif_int_src; /* APBIF_APBIF_INT_SOURCE_0, off 0xF0 */
uint32_t reserved2_int_src; /* RESERVED, offset 0xF4 */
uint32_t i2s_int_set; /* APBIF_I2S_INT_SET_0, offset 0xF8 */
uint32_t dam_int_set; /* APBIF_DAM_INT_SET_0, offset 0xFC */
uint32_t spdif_int_set; /* APBIF_SPDIF_INT_SET_0, off 0x100 */
uint32_t apbif_int_set; /* APBIF_APBIF_INT_SET_0, off 0x104 */
} TegraApbifRegs;
// XBAR functions
static int tegra_ahub_xbar_enable_i2s(TegraAudioHubXbar *xbar, int i2s_id)
{
// Enables I2S as the receiver of APBIF, by writing APBIF_TX0 (0x01) to
// i2s?_rx0.
switch (i2s_id) {
case 0:
writel(1, &xbar->regs->i2s0_rx0);
break;
case 1:
writel(1, &xbar->regs->i2s1_rx0);
break;
case 2:
writel(1, &xbar->regs->i2s2_rx0);
break;
case 3:
writel(1, &xbar->regs->i2s3_rx0);
break;
case 4:
writel(1, &xbar->regs->i2s4_rx0);
break;
default:
printf("%s: Invalid I2S component id: %d\n", __func__, i2s_id);
return 1;
}
return 0;
}
// APBIF FIFO functions
static size_t tegra_ahub_apbif_capacity(TxFifoOps *me)
{
TegraAudioHubApbif *apbif = container_of(me, TegraAudioHubApbif, ops);
// The TxFifoOps.capacity returns capacity in "bytes" while APBIF
// measures capacity in "words".
return apbif->capacity_in_word * sizeof(uint32_t);
}
static int tegra_ahub_apbif_is_full(TxFifoOps *me)
{
TegraAudioHubApbif *apbif = container_of(me, TegraAudioHubApbif, ops);
return readl(&apbif->regs->apbdma_live_stat) & apbif->full_mask;
}
static ssize_t tegra_ahub_apbif_send(TxFifoOps *me, const void *buf, size_t len)
{
const uint32_t *data = (const uint32_t *)buf;
ssize_t written = 0;
TegraApbifRegs *regs = container_of(me, TegraAudioHubApbif, ops)->regs;
if (len % sizeof(*data)) {
printf("%s: Data size (%zd) must be aligned to %zd.\n", __func__,
len, sizeof(*data));
return -1;
}
while (written < len) {
while (tegra_ahub_apbif_is_full(me));
writel(*data++, &regs->channel0_txfifo);
written += sizeof(*data);
}
return written;
}
static void tegra_ahub_apbif_set_cif(TegraAudioHubApbif *apbif, uint32_t value)
{
writel(value, &apbif->regs->channel0_cif_tx0_ctrl);
}
static void tegra_ahub_apbif_enable_channel0(TegraAudioHubApbif *apbif,
int fifo_threshold)
{
uint32_t ctrl = (TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_EN |
TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_16 |
TEGRA_AHUB_CHANNEL_CTRL_TX_EN);
fifo_threshold--; // fifo_threshold starts from 1.
ctrl |= (fifo_threshold << TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT);
writel(ctrl, &apbif->regs->channel0_ctrl);
}
static int tegra_ahub_apbif_set_fifo_mask(TegraAudioHubApbif *apbif)
{
// We use APBIF channel0 as a sender
apbif->full_mask = TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL;
return 0;
}
// Audio hub functions
static uint32_t tegra_ahub_get_cif(int is_receive, uint32_t channels,
uint32_t bits_per_sample,
uint32_t fifo_threshold)
{
uint32_t audio_bits = (bits_per_sample >> 2) - 1;
channels--; // Channels in CIF starts from 1.
fifo_threshold--; // FIFO threshold starts from 1.
// Assume input and output are always using same channel / bits.
return channels << TEGRA_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT |
channels << TEGRA_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT |
audio_bits << TEGRA_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT |
audio_bits << TEGRA_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT |
fifo_threshold << TEGRA_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT |
(is_receive ? TEGRA_AUDIOCIF_DIRECTION_RX <<
TEGRA_AUDIOCIF_CTRL_DIRECTION_SHIFT : 0);
}
static int tegra_audio_hub_enable(SoundRouteComponentOps *me)
{
TegraAudioHub *ahub = container_of(me, TegraAudioHub, component.ops);
uint32_t cif_ctrl = 0;
// FIFO is inactive until (fifo_threshold) of words are sent.
// For better performance, we want to set it to half of capacity.
uint32_t fifo_threshold = ahub->apbif->capacity_in_word / 2;
// Setup audio client interface (ACIF): APBIF (channel0) as sender and
// I2S as receiver.
cif_ctrl = tegra_ahub_get_cif(1, ahub->i2s->channels,
ahub->i2s->bits_per_sample,
fifo_threshold);
tegra_i2s_set_cif_tx_ctrl(ahub->i2s, cif_ctrl);
cif_ctrl = tegra_ahub_get_cif(0, ahub->i2s->channels,
ahub->i2s->bits_per_sample,
fifo_threshold);
tegra_ahub_apbif_set_cif(ahub->apbif, cif_ctrl);
tegra_ahub_apbif_enable_channel0(ahub->apbif, fifo_threshold);
if (tegra_ahub_apbif_set_fifo_mask(ahub->apbif) ||
tegra_ahub_xbar_enable_i2s(ahub->xbar, ahub->i2s->id))
return 1;
return 0;
}
// Constructors
TegraAudioHubXbar *new_tegra_audio_hub_xbar(uintptr_t regs)
{
TegraAudioHubXbar *xbar = xzalloc(sizeof(*xbar));
xbar->regs = (TegraXbarRegs *)regs;
return xbar;
}
TegraAudioHubApbif *new_tegra_audio_hub_apbif(uintptr_t regs,
size_t capacity_in_word)
{
TegraAudioHubApbif *apbif = xzalloc(sizeof(*apbif));
apbif->ops.send = &tegra_ahub_apbif_send;
apbif->ops.is_full = &tegra_ahub_apbif_is_full;
apbif->ops.capacity = &tegra_ahub_apbif_capacity;
apbif->regs = (TegraApbifRegs *)regs;
apbif->capacity_in_word = capacity_in_word;
return apbif;
}
TegraAudioHub *new_tegra_audio_hub(TegraAudioHubXbar *xbar,
TegraAudioHubApbif *apbif,
TegraI2s *i2s)
{
TegraAudioHub *ahub = xzalloc(sizeof(*ahub));
ahub->xbar = xbar;
ahub->apbif = apbif;
ahub->i2s = i2s;
ahub->component.ops.enable = &tegra_audio_hub_enable;
return ahub;
}