blob: 002f695970dbffcf063f88f8e9ccbd00434c6fde [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/hardware/platform/bus/c/banjo.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <ddktl/metadata/audio.h>
#include <soc/aml-common/aml-audio.h>
#include <soc/aml-meson/g12a-clk.h>
#include <soc/aml-s905d2/s905d2-gpio.h>
#include <soc/aml-s905d2/s905d2-hw.h>
#include <ti/ti-audio.h>
#include "astro-gpios.h"
#include "astro.h"
// Enables BT PCM audio.
#define ENABLE_BT
// Enable DAI mode for BT PCM audio.
#define ENABLE_DAI_MODE
// Enable DAI test.
//#define ENABLE_DAI_TEST
#ifdef TAS2770_CONFIG_PATH
#include TAS2770_CONFIG_PATH
#endif
namespace astro {
constexpr uint32_t kCodecVid = PDEV_VID_TI;
constexpr uint32_t kCodecDid = PDEV_DID_TI_TAS2770;
static const pbus_mmio_t audio_mmios[] = {
{.base = S905D2_EE_AUDIO_BASE, .length = S905D2_EE_AUDIO_LENGTH},
};
static const pbus_bti_t tdm_btis[] = {
{
.iommu_index = 0,
.bti_id = BTI_AUDIO_OUT,
},
};
static const zx_bind_inst_t root_match[] = {
BI_MATCH(),
};
static const zx_bind_inst_t i2c_match[] = {
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
BI_ABORT_IF(NE, BIND_I2C_BUS_ID, ASTRO_I2C_3),
BI_MATCH_IF(EQ, BIND_I2C_ADDRESS, I2C_AUDIO_CODEC_ADDR),
};
static const zx_bind_inst_t fault_gpio_match[] = {
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_GPIO),
BI_MATCH_IF(EQ, BIND_GPIO_PIN, GPIO_AUDIO_SOC_FAULT_L),
};
static const zx_bind_inst_t enable_gpio_match[] = {
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_GPIO),
BI_MATCH_IF(EQ, BIND_GPIO_PIN, GPIO_SOC_AUDIO_EN),
};
static const zx_bind_inst_t codec_match[] = {
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CODEC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, kCodecVid),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, kCodecDid),
};
#ifdef ENABLE_BT
#ifdef ENABLE_DAI_MODE
#ifdef ENABLE_DAI_TEST
static const zx_bind_inst_t dai_out_match[] = {
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_DAI),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S905D2),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_DAI_OUT),
};
static const zx_bind_inst_t dai_in_match[] = {
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_DAI),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S905D2),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_DAI_IN),
};
#endif
#endif
#endif
static const device_fragment_part_t i2c_fragment[] = {
{countof(root_match), root_match},
{countof(i2c_match), i2c_match},
};
static const device_fragment_part_t fault_gpio_fragment[] = {
{countof(root_match), root_match},
{countof(fault_gpio_match), fault_gpio_match},
};
static const device_fragment_part_t enable_gpio_fragment[] = {
{countof(root_match), root_match},
{countof(enable_gpio_match), enable_gpio_match},
};
static const device_fragment_part_t codec_fragment[] = {
{countof(root_match), root_match},
{countof(codec_match), codec_match},
};
#ifdef ENABLE_BT
#ifdef ENABLE_DAI_MODE
#ifdef ENABLE_DAI_TEST
static const device_fragment_part_t dai_out_fragment[] = {
{countof(root_match), root_match},
{countof(dai_out_match), dai_out_match},
};
static const device_fragment_part_t dai_in_fragment[] = {
{countof(root_match), root_match},
{countof(dai_in_match), dai_in_match},
};
static const device_fragment_t dai_test_out_fragments[] = {
{"dai-out", countof(dai_out_fragment), dai_out_fragment},
};
static const device_fragment_t dai_test_in_fragments[] = {
{"dai-in", countof(dai_in_fragment), dai_in_fragment},
};
#endif
#else
static const device_fragment_t tdm_pcm_fragments[] = {};
#endif
#endif
static const device_fragment_t tdm_i2s_fragments[] = {
{"gpio-enable", countof(enable_gpio_fragment), enable_gpio_fragment},
{"codec-01", countof(codec_fragment), codec_fragment},
};
static const device_fragment_t codec_fragments[] = {
{"i2c", countof(i2c_fragment), i2c_fragment},
{"gpio", countof(fault_gpio_fragment), fault_gpio_fragment},
};
zx_status_t Astro::AudioInit() {
zx_status_t status;
uint8_t tdm_instance_id = 1;
status = clk_impl_.Disable(g12a_clk::CLK_HIFI_PLL);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Disable(CLK_HIFI_PLL) failed, st = %d", __func__, status);
return status;
}
status = clk_impl_.SetRate(g12a_clk::CLK_HIFI_PLL, 768000000);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: SetRate(CLK_HIFI_PLL) failed, st = %d", __func__, status);
return status;
}
status = clk_impl_.Enable(g12a_clk::CLK_HIFI_PLL);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Enable(CLK_HIFI_PLL) failed, st = %d", __func__, status);
return status;
}
// TDM pin assignments
gpio_impl_.SetAltFunction(S905D2_GPIOA(1), S905D2_GPIOA_1_TDMB_SCLK_FN);
gpio_impl_.SetAltFunction(S905D2_GPIOA(2), S905D2_GPIOA_2_TDMB_FS_FN);
gpio_impl_.SetAltFunction(S905D2_GPIOA(3), S905D2_GPIOA_3_TDMB_D0_FN);
gpio_impl_.SetAltFunction(S905D2_GPIOA(6), S905D2_GPIOA_6_TDMB_DIN3_FN);
constexpr uint64_t ua = 3000;
gpio_impl_.SetDriveStrength(S905D2_GPIOA(1), ua, nullptr);
gpio_impl_.SetDriveStrength(S905D2_GPIOA(2), ua, nullptr);
gpio_impl_.SetDriveStrength(S905D2_GPIOA(3), ua, nullptr);
#ifdef ENABLE_BT
// PCM pin assignments.
gpio_impl_.SetAltFunction(S905D2_GPIOX(8), S905D2_GPIOX_8_TDMA_DIN1_FN);
gpio_impl_.SetAltFunction(S905D2_GPIOX(9), S905D2_GPIOX_8_TDMA_D0_FN);
gpio_impl_.SetAltFunction(S905D2_GPIOX(10), S905D2_GPIOX_10_TDMA_FS_FN);
gpio_impl_.SetAltFunction(S905D2_GPIOX(11), S905D2_GPIOX_11_TDMA_SCLK_FN);
gpio_impl_.SetDriveStrength(S905D2_GPIOX(9), ua, nullptr);
gpio_impl_.SetDriveStrength(S905D2_GPIOX(10), ua, nullptr);
gpio_impl_.SetDriveStrength(S905D2_GPIOX(11), ua, nullptr);
#endif
// PDM pin assignments
gpio_impl_.SetAltFunction(S905D2_GPIOA(7), S905D2_GPIOA_7_PDM_DCLK_FN);
gpio_impl_.SetAltFunction(S905D2_GPIOA(8), S905D2_GPIOA_8_PDM_DIN0_FN);
// Hardware Reset of the codec.
gpio_impl_.ConfigOut(S905D2_GPIOA(5), 0);
zx::nanosleep(zx::deadline_after(zx::msec(1)));
gpio_impl_.ConfigOut(S905D2_GPIOA(5), 1);
// Output devices.
#ifdef ENABLE_BT
// Add TDM OUT for BT.
{
const pbus_bti_t pcm_out_btis[] = {
{
.iommu_index = 0,
.bti_id = BTI_AUDIO_BT_OUT,
},
};
metadata::AmlConfig metadata = {};
snprintf(metadata.manufacturer, sizeof(metadata.manufacturer), "Spacely Sprockets");
snprintf(metadata.product_name, sizeof(metadata.product_name), "astro");
metadata.is_input = false;
// Compatible clocks with other TDM drivers.
metadata.mClockDivFactor = 10;
metadata.sClockDivFactor = 25;
metadata.unique_id = AUDIO_STREAM_UNIQUE_ID_BUILTIN_BT;
metadata.bus = metadata::AmlBus::TDM_A;
metadata.version = metadata::AmlVersion::kS905D2G;
metadata.dai.type = metadata::DaiType::Tdm1;
metadata.dai.sclk_on_raising = true;
metadata.dai.bits_per_sample = 16;
metadata.dai.bits_per_slot = 16;
metadata.ring_buffer.number_of_channels = 1;
metadata.dai.number_of_channels = 1;
metadata.lanes_enable_mask[0] = 1;
pbus_metadata_t tdm_metadata[] = {
{
.type = DEVICE_METADATA_PRIVATE,
.data_buffer = &metadata,
.data_size = sizeof(metadata),
},
};
// Add DAI or controller driver depending on ENABLE_DAI_MODE.
pbus_dev_t tdm_dev = {};
tdm_dev.vid = PDEV_VID_AMLOGIC;
tdm_dev.pid = PDEV_PID_AMLOGIC_S905D2;
tdm_dev.mmio_list = audio_mmios;
tdm_dev.mmio_count = countof(audio_mmios);
tdm_dev.bti_list = pcm_out_btis;
tdm_dev.bti_count = countof(pcm_out_btis);
tdm_dev.metadata_list = tdm_metadata;
tdm_dev.metadata_count = countof(tdm_metadata);
#ifdef ENABLE_DAI_MODE
tdm_dev.name = "astro-pcm-dai-out";
tdm_dev.did = PDEV_DID_AMLOGIC_DAI_OUT;
status = pbus_.DeviceAdd(&tdm_dev);
#else
tdm_dev.name = "astro-pcm-audio-out";
tdm_dev.did = PDEV_DID_AMLOGIC_TDM;
tdm_dev.instance_id = tdm_instance_id++;
status = pbus_.CompositeDeviceAdd(&tdm_dev, tdm_pcm_fragments, countof(tdm_pcm_fragments),
UINT32_MAX);
#endif
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Add DAI/controller driver failed: %d", __FILE__, status);
return status;
}
#ifdef ENABLE_DAI_MODE
#ifdef ENABLE_DAI_TEST
// Add test driver.
bool is_input = false;
const device_metadata_t test_metadata[] = {
{
.type = DEVICE_METADATA_PRIVATE,
.data = &is_input,
.length = sizeof(is_input),
},
};
zx_device_prop_t props[] = {{BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GENERIC},
{BIND_PLATFORM_DEV_DID, 0, PDEV_DID_DAI_TEST}};
composite_device_desc_t comp_desc = {};
comp_desc.props = props;
comp_desc.props_count = countof(props);
comp_desc.coresident_device_index = UINT32_MAX;
comp_desc.fragments = dai_test_out_fragments;
comp_desc.fragments_count = countof(dai_test_out_fragments);
comp_desc.metadata_list = test_metadata;
comp_desc.metadata_count = countof(test_metadata);
status = DdkAddComposite("astro-dai-test-out", &comp_desc);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: PCM CompositeDeviceAdd failed: %d", __FILE__, status);
return status;
}
#endif
#endif
}
#endif
// Add TDM OUT to the codec.
{
zx_device_prop_t props[] = {{BIND_PLATFORM_DEV_VID, 0, kCodecVid},
{BIND_PLATFORM_DEV_DID, 0, kCodecDid}};
metadata::ti::TasConfig metadata = {};
#ifdef TAS2770_CONFIG_PATH
metadata.number_of_writes1 = sizeof(tas2770_init_sequence1) / sizeof(cfg_reg);
for (size_t i = 0; i < metadata.number_of_writes1; ++i) {
metadata.init_sequence1[i].address = tas2770_init_sequence1[i].offset;
metadata.init_sequence1[i].value = tas2770_init_sequence1[i].value;
}
metadata.number_of_writes2 = sizeof(tas2770_init_sequence2) / sizeof(cfg_reg);
for (size_t i = 0; i < metadata.number_of_writes2; ++i) {
metadata.init_sequence2[i].address = tas2770_init_sequence2[i].offset;
metadata.init_sequence2[i].value = tas2770_init_sequence2[i].value;
}
#endif
const device_metadata_t codec_metadata[] = {
{
.type = DEVICE_METADATA_PRIVATE,
.data = reinterpret_cast<uint8_t*>(&metadata),
.length = sizeof(metadata),
},
};
composite_device_desc_t comp_desc = {};
comp_desc.props = props;
comp_desc.props_count = countof(props);
comp_desc.coresident_device_index = UINT32_MAX;
comp_desc.fragments = codec_fragments;
comp_desc.fragments_count = countof(codec_fragments);
comp_desc.metadata_list = codec_metadata;
comp_desc.metadata_count = countof(codec_metadata);
status = DdkAddComposite("audio-codec-tas27xx", &comp_desc);
if (status != ZX_OK) {
zxlogf(ERROR, "%s DdkAddComposite failed %d", __FILE__, status);
return status;
}
}
{
metadata::AmlConfig metadata = {};
snprintf(metadata.manufacturer, sizeof(metadata.manufacturer), "Spacely Sprockets");
snprintf(metadata.product_name, sizeof(metadata.product_name), "astro");
metadata.is_input = false;
// Compatible clocks with other TDM drivers.
metadata.mClockDivFactor = 10;
metadata.sClockDivFactor = 25;
metadata.unique_id = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS;
metadata.bus = metadata::AmlBus::TDM_B;
metadata.version = metadata::AmlVersion::kS905D2G;
metadata.dai.type = metadata::DaiType::I2s;
metadata.ring_buffer.number_of_channels = 1;
metadata.lanes_enable_mask[0] = 1;
metadata.codecs.number_of_codecs = 1;
metadata.codecs.types[0] = metadata::CodecType::Tas27xx;
// Report our external delay based on the chosen frame rate. Note that these
// delays were measured on Astro hardware, and should be pretty good, but they
// will not be perfect. One reason for this is that we are not taking any
// steps to align our start time with start of a TDM frame, which will cause
// up to 1 frame worth of startup error ever time that the output starts.
// Also note that this is really nothing to worry about. Hitting our target
// to within 20.8uSec (for 48k) is pretty good.
metadata.codecs.number_of_external_delays = 2;
metadata.codecs.external_delays[0].frequency = 48'000;
metadata.codecs.external_delays[0].nsecs = ZX_USEC(125);
metadata.codecs.external_delays[1].frequency = 96'000;
metadata.codecs.external_delays[1].nsecs = ZX_NSEC(83333);
metadata.codecs.channels_to_use_bitmask[0] = (1 << 0);
metadata.codecs.delta_gains[0] = -1.5f;
pbus_metadata_t tdm_metadata[] = {
{
.type = DEVICE_METADATA_PRIVATE,
.data_buffer = &metadata,
.data_size = sizeof(metadata),
},
};
pbus_dev_t tdm_dev = {};
tdm_dev.name = "astro-i2s-audio-out";
tdm_dev.vid = PDEV_VID_AMLOGIC;
tdm_dev.pid = PDEV_PID_AMLOGIC_S905D2;
tdm_dev.did = PDEV_DID_AMLOGIC_TDM;
tdm_dev.instance_id = tdm_instance_id++;
tdm_dev.mmio_list = audio_mmios;
tdm_dev.mmio_count = countof(audio_mmios);
tdm_dev.bti_list = tdm_btis;
tdm_dev.bti_count = countof(tdm_btis);
tdm_dev.metadata_list = tdm_metadata;
tdm_dev.metadata_count = countof(tdm_metadata);
status = pbus_.CompositeDeviceAdd(&tdm_dev, tdm_i2s_fragments, countof(tdm_i2s_fragments),
UINT32_MAX);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: I2S CompositeDeviceAdd failed: %d", __FILE__, status);
return status;
}
}
// Input devices.
#ifdef ENABLE_BT
// Add TDM IN for BT.
{
const pbus_bti_t pcm_in_btis[] = {
{
.iommu_index = 0,
.bti_id = BTI_AUDIO_BT_IN,
},
};
metadata::AmlConfig metadata = {};
snprintf(metadata.manufacturer, sizeof(metadata.manufacturer), "Spacely Sprockets");
snprintf(metadata.product_name, sizeof(metadata.product_name), "astro");
metadata.is_input = true;
// Compatible clocks with other TDM drivers.
metadata.mClockDivFactor = 10;
metadata.sClockDivFactor = 25;
metadata.unique_id = AUDIO_STREAM_UNIQUE_ID_BUILTIN_BT;
metadata.bus = metadata::AmlBus::TDM_A;
metadata.version = metadata::AmlVersion::kS905D2G;
metadata.dai.type = metadata::DaiType::Tdm1;
metadata.dai.sclk_on_raising = true;
metadata.dai.bits_per_sample = 16;
metadata.dai.bits_per_slot = 16;
metadata.ring_buffer.number_of_channels = 1;
metadata.dai.number_of_channels = 1;
metadata.swaps = 0x0200;
metadata.lanes_enable_mask[1] = 1;
pbus_metadata_t tdm_metadata[] = {
{
.type = DEVICE_METADATA_PRIVATE,
.data_buffer = &metadata,
.data_size = sizeof(metadata),
},
};
// Add DAI or controller driver depending on ENABLE_DAI_MODE.
pbus_dev_t tdm_dev = {};
tdm_dev.vid = PDEV_VID_AMLOGIC;
tdm_dev.pid = PDEV_PID_AMLOGIC_S905D2;
tdm_dev.mmio_list = audio_mmios;
tdm_dev.mmio_count = countof(audio_mmios);
tdm_dev.bti_list = pcm_in_btis;
tdm_dev.bti_count = countof(pcm_in_btis);
tdm_dev.metadata_list = tdm_metadata;
tdm_dev.metadata_count = countof(tdm_metadata);
#ifdef ENABLE_DAI_MODE
tdm_dev.name = "astro-pcm-dai-in";
tdm_dev.did = PDEV_DID_AMLOGIC_DAI_IN;
status = pbus_.DeviceAdd(&tdm_dev);
#else
tdm_dev.name = "astro-pcm-audio-in";
tdm_dev.did = PDEV_DID_AMLOGIC_TDM;
tdm_dev.instance_id = tdm_instance_id++;
status = pbus_.CompositeDeviceAdd(&tdm_dev, tdm_pcm_fragments, countof(tdm_pcm_fragments),
UINT32_MAX);
#endif
if (status != ZX_OK) {
zxlogf(ERROR, "%s: PCM CompositeDeviceAdd failed: %d", __FILE__, status);
return status;
}
}
#ifdef ENABLE_DAI_MODE
#ifdef ENABLE_DAI_TEST
// Add test driver.
bool is_input = true;
const device_metadata_t test_metadata[] = {
{
.type = DEVICE_METADATA_PRIVATE,
.data = &is_input,
.length = sizeof(is_input),
},
};
zx_device_prop_t props[] = {{BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GENERIC},
{BIND_PLATFORM_DEV_DID, 0, PDEV_DID_DAI_TEST}};
composite_device_desc_t comp_desc = {};
comp_desc.props = props;
comp_desc.props_count = countof(props);
comp_desc.coresident_device_index = UINT32_MAX;
comp_desc.fragments = dai_test_in_fragments;
comp_desc.fragments_count = countof(dai_test_in_fragments);
comp_desc.metadata_list = test_metadata;
comp_desc.metadata_count = countof(test_metadata);
status = DdkAddComposite("astro-dai-test-in", &comp_desc);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: PCM CompositeDeviceAdd failed: %d", __FILE__, status);
return status;
}
#endif
#endif
#endif
// Input device.
{
metadata::AmlPdmConfig metadata = {};
snprintf(metadata.manufacturer, sizeof(metadata.manufacturer), "Spacely Sprockets");
snprintf(metadata.product_name, sizeof(metadata.product_name), "astro");
metadata.number_of_channels = 2;
metadata.version = metadata::AmlVersion::kS905D2G;
metadata.sysClockDivFactor = 4;
metadata.dClockDivFactor = 250;
pbus_metadata_t pdm_metadata[] = {
{
.type = DEVICE_METADATA_PRIVATE,
.data_buffer = &metadata,
.data_size = sizeof(metadata),
},
};
static const pbus_mmio_t pdm_mmios[] = {
{.base = S905D2_EE_PDM_BASE, .length = S905D2_EE_PDM_LENGTH},
{.base = S905D2_EE_AUDIO_BASE, .length = S905D2_EE_AUDIO_LENGTH},
};
static const pbus_bti_t pdm_btis[] = {
{
.iommu_index = 0,
.bti_id = BTI_AUDIO_IN,
},
};
pbus_dev_t dev_in = {};
dev_in.name = "astro-audio-pdm-in";
dev_in.vid = PDEV_VID_AMLOGIC;
dev_in.pid = PDEV_PID_AMLOGIC_S905D2;
dev_in.did = PDEV_DID_AMLOGIC_PDM;
dev_in.mmio_list = pdm_mmios;
dev_in.mmio_count = countof(pdm_mmios);
dev_in.bti_list = pdm_btis;
dev_in.bti_count = countof(pdm_btis);
dev_in.metadata_list = pdm_metadata;
dev_in.metadata_count = countof(pdm_metadata);
status = pbus_.DeviceAdd(&dev_in);
if (status != ZX_OK) {
zxlogf(ERROR, "%s adding audio input device failed %d", __FILE__, status);
return status;
}
}
return ZX_OK;
}
} // namespace astro