| /* |
| * 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++, ®s->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; |
| } |