sound: soc: Add sunxi_v2 for h616 ahub

This commit is contained in:
orangepi-xunlong 2023-06-05 14:38:46 +08:00
parent a86c734824
commit cfe0a8df18
17 changed files with 3889 additions and 17 deletions

View File

@ -215,20 +215,22 @@ static struct ccu_nkmp pll_de_clk = {
},
};
/*
* TODO: Determine SDM settings for the audio PLL. The manual suggests
* PLL_FACTOR_N=16, PLL_POST_DIV_P=2, OUTPUT_DIV=2, pattern=0xe000c49b
* for 24.576 MHz, and PLL_FACTOR_N=22, PLL_POST_DIV_P=3, OUTPUT_DIV=2,
* pattern=0xe001288c for 22.5792 MHz.
* This clashes with our fixed PLL_POST_DIV_P.
*/
#define SUN50I_H616_PLL_AUDIO_REG 0x078
static struct ccu_sdm_setting pll_audio_sdm_table[] = {
{ .rate = 90316800, .pattern = 0xc001288d, .m = 3, .n = 22 },
{ .rate = 98304000, .pattern = 0xc001eb85, .m = 5, .n = 40 },
};
static struct ccu_nm pll_audio_hs_clk = {
.enable = BIT(31),
.lock = BIT(28),
.n = _SUNXI_CCU_MULT_MIN(8, 8, 12),
.m = _SUNXI_CCU_DIV(1, 1), /* input divider */
.m = _SUNXI_CCU_DIV(16, 6),
.sdm = _SUNXI_CCU_SDM(pll_audio_sdm_table,
BIT(24), 0x178, BIT(31)),
.common = {
.features = CCU_FEATURE_SIGMA_DELTA_MOD,
.reg = 0x078,
.hw.init = CLK_HW_INIT("pll-audio-hs", "osc24M",
&ccu_nm_ops,
@ -688,13 +690,13 @@ static const struct clk_hw *clk_parent_pll_audio[] = {
*/
static CLK_FIXED_FACTOR_HWS(pll_audio_1x_clk, "pll-audio-1x",
clk_parent_pll_audio,
96, 1, CLK_SET_RATE_PARENT);
4, 1, CLK_SET_RATE_PARENT);
static CLK_FIXED_FACTOR_HWS(pll_audio_2x_clk, "pll-audio-2x",
clk_parent_pll_audio,
48, 1, CLK_SET_RATE_PARENT);
2, 1, CLK_SET_RATE_PARENT);
static CLK_FIXED_FACTOR_HWS(pll_audio_4x_clk, "pll-audio-4x",
clk_parent_pll_audio,
24, 1, CLK_SET_RATE_PARENT);
1, 1, CLK_SET_RATE_PARENT);
static const struct clk_hw *pll_periph0_parents[] = {
&pll_periph0_clk.common.hw
@ -1130,13 +1132,10 @@ static int sun50i_h616_ccu_probe(struct platform_device *pdev)
writel(val, reg + usb2_clk_regs[i]);
}
/*
* Force the post-divider of pll-audio to 12 and the output divider
* of it to 2, so 24576000 and 22579200 rates can be set exactly.
*/
val = readl(reg + SUN50I_H616_PLL_AUDIO_REG);
val &= ~(GENMASK(21, 16) | BIT(0));
writel(val | (11 << 16) | BIT(0), reg + SUN50I_H616_PLL_AUDIO_REG);
val &= ~BIT(1);
val |= BIT(0);
writel(val, reg + SUN50I_H616_PLL_AUDIO_REG);
/*
* First clock parent (osc32K) is unusable for CEC. But since there

View File

@ -94,6 +94,7 @@ source "sound/soc/sprd/Kconfig"
source "sound/soc/sti/Kconfig"
source "sound/soc/stm/Kconfig"
source "sound/soc/sunxi/Kconfig"
source "sound/soc/sunxi_v2/Kconfig"
source "sound/soc/tegra/Kconfig"
source "sound/soc/ti/Kconfig"
source "sound/soc/uniphier/Kconfig"

View File

@ -62,6 +62,7 @@ obj-$(CONFIG_SND_SOC) += sprd/
obj-$(CONFIG_SND_SOC) += sti/
obj-$(CONFIG_SND_SOC) += stm/
obj-$(CONFIG_SND_SOC) += sunxi/
obj-$(CONFIG_SND_SOC) += sunxi_v2/
obj-$(CONFIG_SND_SOC) += tegra/
obj-$(CONFIG_SND_SOC) += ti/
obj-$(CONFIG_SND_SOC) += uniphier/

View File

@ -0,0 +1,48 @@
# common
config SND_SOC_SUNXI_MACH
tristate
# ahub dam
config SND_SOC_SUNXI_AHUB_DAM
tristate
config SND_SOC_SUNXI_INTERNALCODEC
tristate
config SND_SOC_SUNXI_SUN50IW9_CODEC
tristate
# menu select
menu "Allwinner SoC Audio support V2"
depends on ARCH_SUNXI
# aaudio
config SND_SOC_SUNXI_AAUDIO
tristate "Allwinner AAUDIO support"
select REGMAP_MMIO
select SND_SOC_GENERIC_DMAENGINE_PCM
select SND_SOC_SUNXI_MACH
select SND_SOC_SUNXI_INTERNALCODEC
select SND_SOC_SUNXI_SUN50IW9_CODEC
depends on ARCH_SUNXI
help
Select Y or M to support analog-audio Module in the Allwinner SoCs.
# ahub
config SND_SOC_SUNXI_AHUB
tristate "Allwinner AHUB Support"
select REGMAP_MMIO
select SND_SOC_GENERIC_DMAENGINE_PCM
select SND_SOC_SUNXI_MACH
select SND_SOC_SUNXI_AHUB_DAM
depends on ARCH_SUNXI
help
Select Y or M to support audio-hub Module in Allwinner SoCs.
config SND_SOC_SUNXI_DEBUG
tristate "Components Debug"
depends on SND_SOC_SUNXI_COMPONENTS
help
Select Y or M to support debug components.
endmenu

View File

@ -0,0 +1,11 @@
# platform -> ahub
snd_soc_sunxi_ahub_dam-objs += snd_sunxi_ahub_dam.o
obj-$(CONFIG_SND_SOC_SUNXI_AHUB_DAM) += snd_soc_sunxi_ahub_dam.o
snd_soc_sunxi_ahub-objs += snd_sunxi_ahub.o
obj-$(CONFIG_SND_SOC_SUNXI_AHUB) += snd_soc_sunxi_ahub.o
# common -> machine (note: Finally compile, save system startup time)
snd_soc_sunxi_machine-objs += snd_sunxi_mach.o
snd_soc_sunxi_machine-objs += snd_sunxi_mach_utils.o
obj-$(CONFIG_SND_SOC_SUNXI_MACH) += snd_soc_sunxi_machine.o

View File

@ -0,0 +1,63 @@
/*
* Allwinner SoCs hdmi driver.
*
* Copyright (C) 2016 Allwinner.
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#ifndef __DRV_HDMI_H__
#define __DRV_HDMI_H__
typedef struct {
__u8 hw_intf; /* 0:iis 1:spdif 2:pcm */
__u16 fs_between; /* fs */
__u32 sample_rate; /*sample rate*/
__u8 clk_edge; /* 0:*/
__u8 ch0_en; /* 1 */
__u8 ch1_en; /* 0 */
__u8 ch2_en; /* 0 */
__u8 ch3_en; /* 0 */
__u8 word_length; /* 32 */
__u8 shift_ctl; /* 0 */
__u8 dir_ctl; /* 0 */
__u8 ws_pol;
__u8 just_pol;
__u8 channel_num;
__u8 data_raw;
__u8 sample_bit;
__u8 ca; /* channel allocation */
} hdmi_audio_t;
typedef struct {
__s32 (*hdmi_audio_enable)(__u8 mode, __u8 channel);
__s32 (*hdmi_set_audio_para)(hdmi_audio_t *audio_para);
__s32 (*hdmi_is_playback)(void);
} __audio_hdmi_func;
enum hdmi_hpd_status {
STATUE_CLOSE = 0,
STATUE_OPEN = 1,
};
void audio_set_hdmi_func(__audio_hdmi_func *hdmi_func);
#if defined(CONFIG_SND_SUNXI_SOC_AUDIOHUB_INTERFACE)
void audio_set_muti_hdmi_func(__audio_hdmi_func *hdmi_func);
#endif
/******************** SND_HDMI for sunxi_v2 begain ***************************/
#if IS_ENABLED(CONFIG_HDMI2_DISP2_SUNXI)
extern int snd_hdmi_get_func(__audio_hdmi_func *hdmi_func);
#else
static inline int snd_hdmi_get_func(__audio_hdmi_func *hdmi_func)
{
pr_err("HDMI Audio API is disable\n");
return -1;
}
#endif
/******************** SND_HDMI for sunxi_v2 end ******************************/
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
/* sound\soc\sunxi\snd_sunxi_ahub.h
* (C) Copyright 2021-2025
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* 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.
*/
#ifndef __SND_SUNXI_AHUB_H
#define __SND_SUNXI_AHUB_H
#include "snd_sunxi_ahub_dam.h"
struct sunxi_ahub_pinctl_info {
struct pinctrl *pinctrl;
struct pinctrl_state *pinstate;
struct pinctrl_state *pinstate_sleep;
bool pinctrl_used;
};
struct sunxi_ahub_dts_info {
unsigned int dai_type;
unsigned int apb_num;
unsigned int tdm_num;
unsigned int tx_pin;
unsigned int rx_pin;
/* value must be (2^n)Kbyte */
size_t playback_cma;
size_t playback_fifo_size;
size_t capture_cma;
size_t capture_fifo_size;
};
struct sunxi_ahub_regulator_info {
struct regulator *regulator;
const char *regulator_name;
};
struct sunxi_ahub_info {
struct device *dev;
struct sunxi_ahub_mem_info mem_info;
struct sunxi_ahub_clk_info clk_info;
struct sunxi_ahub_pinctl_info pin_info;
struct sunxi_ahub_dts_info dts_info;
struct sunxi_ahub_regulator_info regulator_info;
//struct sunxi_dma_params playback_dma_param;
//struct sunxi_dma_params capture_dma_param;
struct snd_dmaengine_dai_dma_data playback_dma_param;
struct snd_dmaengine_dai_dma_data capture_dma_param;
/* for Hardware param setting */
unsigned int fmt;
unsigned int pllclk_freq;
unsigned int moduleclk_freq;
unsigned int mclk_freq;
unsigned int lrck_freq;
unsigned int bclk_freq;
};
#endif /* __SND_SUNXI_AHUB_H */

View File

@ -0,0 +1,534 @@
/*
* sound\soc\sunxi\snd_sunxi_ahub_dam.c
* (C) Copyright 2021-2025
* AllWinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* 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.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/regmap.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <sound/soc.h>
#include "snd_sunxi_log.h"
#include "snd_sunxi_ahub_dam.h"
#define HLOG "AHUB_DAM"
#define DRV_NAME "sunxi-snd-plat-ahub_dam"
static struct resource g_res;
struct sunxi_ahub_mem_info g_mem_info = {
.res = &g_res,
};
static struct sunxi_ahub_clk_info g_clk_info;
static struct regmap_config g_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUNXI_AHUB_MAX_REG,
.cache_type = REGCACHE_NONE,
};
static struct snd_soc_dai_driver sunxi_ahub_dam_dai = {
.name = "ahub_dam",
};
static int sunxi_ahub_dam_probe(struct snd_soc_component *component)
{
SND_LOG_DEBUG(HLOG, "\n");
return 0;
}
static int sunxi_ahub_dam_suspend(struct snd_soc_component *component)
{
struct sunxi_ahub_clk_info *clk_info = &g_clk_info;
SND_LOG_DEBUG(HLOG, "\n");
clk_disable_unprepare(clk_info->clk_module);
clk_disable_unprepare(clk_info->clk_pll);
//clk_disable_unprepare(clk_info->clk_pllx4);
clk_disable_unprepare(clk_info->clk_bus);
reset_control_assert(clk_info->clk_rst);
return 0;
}
static int sunxi_ahub_dam_resume(struct snd_soc_component *component)
{
struct sunxi_ahub_clk_info *clk_info = &g_clk_info;
SND_LOG_DEBUG(HLOG, "\n");
if (reset_control_deassert(clk_info->clk_rst)) {
SND_LOG_ERR(HLOG, "clk rst deassert failed\n");
return -EINVAL;
}
if (clk_prepare_enable(clk_info->clk_bus)) {
SND_LOG_ERR(HLOG, "clk bus enable failed\n");
return -EBUSY;
}
if (clk_prepare_enable(clk_info->clk_pll)) {
SND_LOG_ERR(HLOG, "clk_pll enable failed\n");
return -EBUSY;
}
//if (clk_prepare_enable(clk_info->clk_pllx4)) {
// SND_LOG_ERR(HLOG, "clk_pllx4 enable failed\n");
// return -EBUSY;
//}
if (clk_prepare_enable(clk_info->clk_module)) {
SND_LOG_ERR(HLOG, "clk_module enable failed\n");
return -EBUSY;
}
return 0;
}
struct str_conv {
char *str;
unsigned int reg;
};
static struct str_conv ahub_mux_name[] = {
{"APBIF0 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(0)},
{"APBIF1 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(1)},
{"APBIF2 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(2)},
{"I2S0 Src Select", SUNXI_AHUB_I2S_RXCONT(0)},
{"I2S1 Src Select", SUNXI_AHUB_I2S_RXCONT(1)},
{"I2S2 Src Select", SUNXI_AHUB_I2S_RXCONT(2)},
{"I2S3 Src Select", SUNXI_AHUB_I2S_RXCONT(3)},
{"DAM0C0 Src Select", SUNXI_AHUB_DAM_RX0_SRC(0)},
{"DAM0C1 Src Select", SUNXI_AHUB_DAM_RX1_SRC(0)},
{"DAM0C2 Src Select", SUNXI_AHUB_DAM_RX2_SRC(0)},
{"DAM1C0 Src Select", SUNXI_AHUB_DAM_RX0_SRC(1)},
{"DAM1C1 Src Select", SUNXI_AHUB_DAM_RX1_SRC(1)},
{"DAM1C2 Src Select", SUNXI_AHUB_DAM_RX2_SRC(1)},
};
static const char *ahub_mux_text[] = {
"NONE",
"APBIF_TXDIF0",
"APBIF_TXDIF1",
"APBIF_TXDIF2",
"I2S0_TXDIF",
"I2S1_TXDIF",
"I2S2_TXDIF",
"I2S3_TXDIF",
"DAM0_TXDIF",
"DAM1_TXDIF",
};
static const unsigned int ahub_mux_values[] = {
0,
1 << I2S_RX_APBIF_TXDIF0,
1 << I2S_RX_APBIF_TXDIF1,
1 << I2S_RX_APBIF_TXDIF2,
1 << I2S_RX_I2S0_TXDIF,
1 << I2S_RX_I2S1_TXDIF,
1 << I2S_RX_I2S2_TXDIF,
1 << I2S_RX_I2S3_TXDIF,
1 << I2S_RX_DAM0_TXDIF,
1 << I2S_RX_DAM1_TXDIF,
};
static SOC_ENUM_SINGLE_EXT_DECL(ahub_mux, ahub_mux_text);
static int sunxi_ahub_mux_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
unsigned int reg_val;
unsigned int src_reg;
struct regmap *regmap = g_mem_info.regmap;
for (i = 0; i < ARRAY_SIZE(ahub_mux_name); i++) {
if (!strncmp(ahub_mux_name[i].str, kcontrol->id.name,
strlen(ahub_mux_name[i].str))) {
src_reg = ahub_mux_name[i].reg;
regmap_read(regmap, src_reg, &reg_val);
reg_val &= 0xffffc000;
break;
}
}
for (i = 1; i < ARRAY_SIZE(ahub_mux_values); i++) {
if (reg_val & ahub_mux_values[i]) {
ucontrol->value.integer.value[0] = i;
return 0;
}
}
ucontrol->value.integer.value[0] = 0;
return 0;
}
static int sunxi_ahub_mux_set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
unsigned int src_reg, src_regbit;
struct regmap *regmap = g_mem_info.regmap;
if (ucontrol->value.integer.value[0] > ARRAY_SIZE(ahub_mux_name))
return -EINVAL;
src_regbit = ahub_mux_values[ucontrol->value.integer.value[0]];
for (i = 0; i < ARRAY_SIZE(ahub_mux_name); i++) {
if (!strncmp(ahub_mux_name[i].str, kcontrol->id.name,
strlen(ahub_mux_name[i].str))) {
src_reg = ahub_mux_name[i].reg;
regmap_update_bits(regmap, src_reg, 0xffffc000, src_regbit);
break;
}
}
return 0;
}
static const struct snd_kcontrol_new sunxi_ahub_dam_controls[] = {
SOC_ENUM_EXT("APBIF0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("APBIF1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("APBIF2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("I2S0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("I2S1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("I2S2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("I2S3 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("DAM0C0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("DAM0C1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("DAM0C2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("DAM1C0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("DAM1C1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
SOC_ENUM_EXT("DAM1C2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
};
static struct snd_soc_component_driver sunxi_ahub_dam_dev = {
.name = DRV_NAME,
.probe = sunxi_ahub_dam_probe,
.suspend = sunxi_ahub_dam_suspend,
.resume = sunxi_ahub_dam_resume,
.controls = sunxi_ahub_dam_controls,
.num_controls = ARRAY_SIZE(sunxi_ahub_dam_controls),
};
/*******************************************************************************
* for kernel source
******************************************************************************/
int snd_soc_sunxi_ahub_mem_get(struct sunxi_ahub_mem_info *mem_info)
{
SND_LOG_DEBUG(HLOG, "\n");
if (IS_ERR_OR_NULL(g_mem_info.regmap)) {
SND_LOG_ERR(HLOG, "regmap is invalid\n");
return -EINVAL;
}
if (IS_ERR_OR_NULL(g_mem_info.res)) {
SND_LOG_ERR(HLOG, "res is invalid\n");
return -EINVAL;
}
mem_info->regmap = g_mem_info.regmap;
mem_info->res = g_mem_info.res;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_sunxi_ahub_mem_get);
int snd_soc_sunxi_ahub_clk_get(struct sunxi_ahub_clk_info *clk_info)
{
SND_LOG_DEBUG(HLOG, "\n");
if (IS_ERR_OR_NULL(g_clk_info.clk_pll)) {
SND_LOG_ERR(HLOG, "clk_pll is invalid\n");
return -EINVAL;
}
//if (IS_ERR_OR_NULL(g_clk_info.clk_pllx4)) {
// SND_LOG_ERR(HLOG, "clk_pllx4 is invalid\n");
// return -EINVAL;
//}
if (IS_ERR_OR_NULL(g_clk_info.clk_module)) {
SND_LOG_ERR(HLOG, "clk_module is invalid\n");
return -EINVAL;
}
clk_info->clk_pll = g_clk_info.clk_pll;
//clk_info->clk_pllx4 = g_clk_info.clk_pllx4;
clk_info->clk_module = g_clk_info.clk_module;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_sunxi_ahub_clk_get);
static int snd_soc_sunxi_ahub_mem_init(struct platform_device *pdev,
struct device_node *np,
struct sunxi_ahub_mem_info *mem_info)
{
int ret = 0;
SND_LOG_DEBUG(HLOG, "\n");
ret = of_address_to_resource(np, 0, mem_info->res);
if (ret) {
SND_LOG_ERR(HLOG, "parse device node resource failed\n");
ret = -EINVAL;
goto err_of_addr_to_resource;
}
mem_info->memregion = devm_request_mem_region(&pdev->dev,
mem_info->res->start,
resource_size(mem_info->res),
DRV_NAME);
if (IS_ERR_OR_NULL(mem_info->memregion)) {
SND_LOG_ERR(HLOG, "memory region already claimed\n");
ret = -EBUSY;
goto err_devm_request_region;
}
mem_info->membase = devm_ioremap(&pdev->dev,
mem_info->memregion->start,
resource_size(mem_info->memregion));
if (IS_ERR_OR_NULL(mem_info->membase)) {
SND_LOG_ERR(HLOG, "ioremap failed\n");
ret = -EBUSY;
goto err_devm_ioremap;
}
mem_info->regmap = devm_regmap_init_mmio(&pdev->dev,
mem_info->membase,
&g_regmap_config);
if (IS_ERR_OR_NULL(mem_info->regmap)) {
SND_LOG_ERR(HLOG, "regmap init failed\n");
ret = -EINVAL;
goto err_devm_regmap_init;
}
return 0;
err_devm_regmap_init:
devm_iounmap(&pdev->dev, mem_info->membase);
err_devm_ioremap:
devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
resource_size(mem_info->memregion));
err_devm_request_region:
err_of_addr_to_resource:
return ret;
};
static int snd_soc_sunxi_ahub_clk_init(struct platform_device *pdev,
struct device_node *np,
struct sunxi_ahub_clk_info *clk_info)
{
int ret = 0;
SND_LOG_DEBUG(HLOG, "\n");
/* deassert rst clk */
clk_info->clk_rst = devm_reset_control_get(&pdev->dev, NULL);
if (IS_ERR_OR_NULL(clk_info->clk_rst)) {
SND_LOG_ERR(HLOG, "clk rst get failed\n");
ret = -EBUSY;
goto err_rst_clk;
}
if (reset_control_deassert(clk_info->clk_rst)) {
SND_LOG_ERR(HLOG, "deassert reset clk failed\n");
ret = -EBUSY;
goto err_rst_clk;
}
/* enable ahub bus clk */
clk_info->clk_bus = of_clk_get_by_name(np, "clk_bus_audio_hub");
if (IS_ERR_OR_NULL(clk_info->clk_bus)) {
SND_LOG_ERR(HLOG, "clk bus get failed\n");
ret = -EBUSY;
goto err_bus_clk;
}
if (clk_prepare_enable(clk_info->clk_bus)) {
SND_LOG_ERR(HLOG, "ahub clk bus enable failed\n");
ret = -EBUSY;
goto err_bus_clk;
}
/* get clk of ahub */
clk_info->clk_module = of_clk_get_by_name(np, "clk_audio_hub");
if (IS_ERR_OR_NULL(clk_info->clk_module)) {
SND_LOG_ERR(HLOG, "clk module get failed\n");
ret = -EBUSY;
goto err_module_clk;
}
clk_info->clk_pll = of_clk_get_by_name(np, "clk_pll_audio");
if (IS_ERR_OR_NULL(clk_info->clk_pll)) {
SND_LOG_ERR(HLOG, "clk pll get failed\n");
ret = -EBUSY;
goto err_pll_clk;
}
//clk_info->clk_pllx4 = of_clk_get_by_name(np, "clk_pll_audio_4x");
//if (IS_ERR_OR_NULL(clk_info->clk_pllx4)) {
// SND_LOG_ERR(HLOG, "clk pllx4 get failed\n");
// ret = -EBUSY;
// goto err_pllx4_clk;
//}
/* set ahub clk parent */
//if (clk_set_parent(clk_info->clk_module, clk_info->clk_pllx4)) {
// SND_LOG_ERR(HLOG, "set parent of clk_module to pllx4 failed\n");
// ret = -EINVAL;
// goto err_set_parent_clk;
//}
/* enable clk of ahub */
if (clk_prepare_enable(clk_info->clk_pll)) {
SND_LOG_ERR(HLOG, "clk_pll enable failed\n");
ret = -EBUSY;
goto err_pll_clk_enable;
}
//if (clk_prepare_enable(clk_info->clk_pllx4)) {
// SND_LOG_ERR(HLOG, "clk_pllx4 enable failed\n");
// ret = -EBUSY;
// goto err_pllx4_clk_enable;
//}
if (clk_prepare_enable(clk_info->clk_module)) {
SND_LOG_ERR(HLOG, "clk_module enable failed\n");
ret = -EBUSY;
goto err_module_clk_enable;
}
return 0;
err_module_clk_enable:
// clk_disable_unprepare(clk_info->clk_pllx4);
//err_pllx4_clk_enable:
clk_disable_unprepare(clk_info->clk_pll);
err_pll_clk_enable:
//err_set_parent_clk:
// clk_put(clk_info->clk_pllx4);
//err_pllx4_clk:
// clk_put(clk_info->clk_pll);
err_pll_clk:
clk_put(clk_info->clk_module);
err_module_clk:
clk_disable_unprepare(clk_info->clk_bus);
clk_put(clk_info->clk_bus);
err_bus_clk:
reset_control_assert(clk_info->clk_rst);
err_rst_clk:
return ret;
}
static int sunxi_ahub_dam_dev_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
SND_LOG_DEBUG(HLOG, "\n");
ret = snd_soc_sunxi_ahub_mem_init(pdev, np, &g_mem_info);
if (ret) {
SND_LOG_ERR(HLOG, "remap init failed\n");
ret = -EINVAL;
goto err_snd_soc_sunxi_ahub_mem_init;
}
ret = snd_soc_sunxi_ahub_clk_init(pdev, np, &g_clk_info);
if (ret) {
SND_LOG_ERR(HLOG, "clk init failed\n");
ret = -EINVAL;
goto err_snd_soc_sunxi_ahub_clk_init;
}
ret = snd_soc_register_component(&pdev->dev,
&sunxi_ahub_dam_dev,
&sunxi_ahub_dam_dai, 1);
if (ret) {
SND_LOG_ERR(HLOG, "component register failed\n");
ret = -ENOMEM;
goto err_snd_soc_register_component;
}
SND_LOG_DEBUG(HLOG, "register ahub_dam platform success\n");
return 0;
err_snd_soc_register_component:
err_snd_soc_sunxi_ahub_clk_init:
err_snd_soc_sunxi_ahub_mem_init:
of_node_put(np);
return ret;
}
static int sunxi_ahub_dam_dev_remove(struct platform_device *pdev)
{
struct sunxi_ahub_mem_info *mem_info = &g_mem_info;
struct sunxi_ahub_clk_info *clk_info = &g_clk_info;
SND_LOG_DEBUG(HLOG, "\n");
snd_soc_unregister_component(&pdev->dev);
devm_iounmap(&pdev->dev, mem_info->membase);
devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
resource_size(mem_info->memregion));
clk_disable_unprepare(clk_info->clk_module);
clk_put(clk_info->clk_module);
clk_disable_unprepare(clk_info->clk_pll);
clk_put(clk_info->clk_pll);
//clk_disable_unprepare(clk_info->clk_pllx4);
//clk_put(clk_info->clk_pllx4);
clk_disable_unprepare(clk_info->clk_bus);
clk_put(clk_info->clk_bus);
reset_control_assert(clk_info->clk_rst);
SND_LOG_DEBUG(HLOG, "unregister ahub_dam platform success\n");
return 0;
}
static const struct of_device_id sunxi_ahub_dam_of_match[] = {
{ .compatible = "allwinner," DRV_NAME, },
{},
};
MODULE_DEVICE_TABLE(of, sunxi_ahub_dam_of_match);
static struct platform_driver sunxi_ahub_dam_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = sunxi_ahub_dam_of_match,
},
.probe = sunxi_ahub_dam_dev_probe,
.remove = sunxi_ahub_dam_dev_remove,
};
int __init sunxi_ahub_dam_dev_init(void)
{
int ret;
ret = platform_driver_register(&sunxi_ahub_dam_driver);
if (ret != 0) {
SND_LOG_ERR(HLOG, "platform driver register failed\n");
return -EINVAL;
}
return ret;
}
void __exit sunxi_ahub_dam_dev_exit(void)
{
platform_driver_unregister(&sunxi_ahub_dam_driver);
}
late_initcall(sunxi_ahub_dam_dev_init);
module_exit(sunxi_ahub_dam_dev_exit);
MODULE_AUTHOR("Dby@allwinnertech.com");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("sunxi soundcard platform of ahub_dam");

View File

@ -0,0 +1,291 @@
/* sound\soc\sunxi\snd_sunxi_ahub_dam.h
* (C) Copyright 2021-2025
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* some simple description for this code
*
* 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.
*
*/
#ifndef __SND_SUNXI_AHUB_DAM_H
#define __SND_SUNXI_AHUB_DAM_H
/* SUNXI Audio Hub registers list */
#define SUNXI_AHUB_CTL 0x00
#define SUNXI_AHUB_VER 0x04
#define SUNXI_AHUB_RST 0x08
#define SUNXI_AHUB_GAT 0x0c
#define SUNXI_AHUB_APBIF_TX_CTL(n) (0x10 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_TX_IRQ_CTL(n) (0x14 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_TX_IRQ_STA(n) (0x18 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_TXFIFO_CTL(n) (0x20 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_TXFIFO_STA(n) (0x24 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_TXFIFO(n) (0x30 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_TXFIFO_CNT(n) (0x34 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RX_CTL(n) (0x100 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RX_IRQ_CTL(n) (0x104 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RX_IRQ_STA(n) (0x108 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RXFIFO_CTL(n) (0x110 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RXFIFO_STA(n) (0x114 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RXFIFO_CONT(n) (0x118 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RXFIFO(n) (0x120 + ((n) * 0x30))
#define SUNXI_AHUB_APBIF_RXFIFO_CNT(n) (0x124 + ((n) * 0x30))
#define SUNXI_AHUB_I2S_CTL(n) (0x200 + ((n) << 8))
#define SUNXI_AHUB_I2S_FMT0(n) (0x204 + ((n) << 8))
#define SUNXI_AHUB_I2S_FMT1(n) (0x208 + ((n) << 8))
#define SUNXI_AHUB_I2S_CLKD(n) (0x20c + ((n) << 8))
#define SUNXI_AHUB_I2S_RXCONT(n) (0x220 + ((n) << 8))
#define SUNXI_AHUB_I2S_CHCFG(n) (0x224 + ((n) << 8))
#define SUNXI_AHUB_I2S_IRQ_CTL(n) (0x228 + ((n) << 8))
#define SUNXI_AHUB_I2S_IRQ_STA(n) (0x22C + ((n) << 8))
#define SUNXI_AHUB_I2S_OUT_SLOT(n, m) (0x230 + ((n) << 8) + ((m) << 4))
#define SUNXI_AHUB_I2S_OUT_CHMAP0(n, m) (0x234 + ((n) << 8) + ((m) << 4))
#define SUNXI_AHUB_I2S_OUT_CHMAP1(n, m) (0x238 + ((n) << 8) + ((m) << 4))
#define SUNXI_AHUB_I2S_IN_SLOT(n) (0x270 + ((n) << 8))
#define SUNXI_AHUB_I2S_IN_CHMAP0(n) (0x274 + ((n) << 8))
#define SUNXI_AHUB_I2S_IN_CHMAP1(n) (0x278 + ((n) << 8))
#define SUNXI_AHUB_I2S_IN_CHMAP2(n) (0x27C + ((n) << 8))
#define SUNXI_AHUB_I2S_IN_CHMAP3(n) (0x280 + ((n) << 8))
#define SUNXI_AHUB_DAM_CTL(n) (0xA00 + ((n) << 7))
#define SUNXI_AHUB_DAM_RX0_SRC(n) (0xA10 + ((n) << 7))
#define SUNXI_AHUB_DAM_RX1_SRC(n) (0xA14 + ((n) << 7))
#define SUNXI_AHUB_DAM_RX2_SRC(n) (0xA18 + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL0(n) (0xA30 + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL1(n) (0xA34 + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL2(n) (0xA38 + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL3(n) (0xA3C + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL4(n) (0xA40 + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL5(n) (0xA44 + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL6(n) (0xA48 + ((n) << 7))
#define SUNXI_AHUB_DAM_MIX_CTL7(n) (0xA4C + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL0(n) (0xA50 + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL1(n) (0xA54 + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL2(n) (0xA58 + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL3(n) (0xA5C + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL4(n) (0xA60 + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL5(n) (0xA64 + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL6(n) (0xA68 + ((n) << 7))
#define SUNXI_AHUB_DAM_GAIN_CTL7(n) (0xA6C + ((n) << 7))
#define SUNXI_AHUB_MAX_REG SUNXI_AHUB_DAM_GAIN_CTL7(1)
/* SUNXI_AHUB_CTL */
#define HDMI_SRC_SEL 0x04
/* SUNXI_AHUB_RST */
#define APBIF_TXDIF0_RST 31
#define APBIF_TXDIF1_RST 30
#define APBIF_TXDIF2_RST 29
#define APBIF_RXDIF0_RST 27
#define APBIF_RXDIF1_RST 26
#define APBIF_RXDIF2_RST 25
#define I2S0_RST 23
#define I2S1_RST 22
#define I2S2_RST 21
#define I2S3_RST 20
#define DAM0_RST 15
#define DAM1_RST 14
/* SUNXI_AHUB_GAT */
#define APBIF_TXDIF0_GAT 31
#define APBIF_TXDIF1_GAT 30
#define APBIF_TXDIF2_GAT 29
#define APBIF_RXDIF0_GAT 27
#define APBIF_RXDIF1_GAT 26
#define APBIF_RXDIF2_GAT 25
#define I2S0_GAT 23
#define I2S1_GAT 22
#define I2S2_GAT 21
#define I2S3_GAT 20
#define DAM0_GAT 15
#define DAM1_GAT 14
/* SUNXI_AHUB_APBIF_TX_CTL */
#define APBIF_TX_WS 16
#define APBIF_TX_CHAN_NUM 8
#define APBIF_TX_START 4
/* SUNXI_AHUB_APBIF_TX_IRQ_CTL */
#define APBIF_TX_DRQ 3
#define APBIF_TX_OVEN 1
#define APBIF_TX_EMEN 0
/* SUNXI_AHUB_APBIF_TX_IRQ_STA */
#define APBIF_TX_OV_PEND 2
#define APBIF_TX_EM_PEND 0
/* SUNXI_AHUB_APBIF_TXFIFO_CTL */
#define APBIF_TX_FTX 12
#define APBIF_TX_LEVEL 4
#define APBIF_TX_TXIM 0
/* SUNXI_AHUB_APBIF_TXFIFO_STA */
#define APBIF_TX_EMPTY 8
#define APBIF_TX_EMCNT 0
/* SUNXI_AHUB_APBIF_RX_CTL */
#define APBIF_RX_WS 16
#define APBIF_RX_CHAN_NUM 8
#define APBIF_RX_START 4
/* SUNXI_AHUB_APBIF_RX_IRQ_CTL */
#define APBIF_RX_DRQ 3
#define APBIF_RX_UVEN 2
#define APBIF_RX_AVEN 0
/* SUNXI_AHUB_APBIF_RX_IRQ_STA */
#define APBIF_RX_UV_PEND 2
#define APBIF_RX_AV_PEND 0
/* SUNXI_AHUB_APBIF_RXFIFO_CTL */
#define APBIF_RX_FRX 12
#define APBIF_RX_LEVEL 4
#define APBIF_RX_RXOM 0
/* SUNXI_AHUB_APBIF_RXFIFO_STA */
#define APBIF_RX_AVAIL 8
#define APBIF_RX_AVCNT 0
/* SUNXI_AHUB_APBIF_RXFIFO_CONT */
#define APBIF_RX_APBIF_TXDIF0 31
#define APBIF_RX_APBIF_TXDIF1 30
#define APBIF_RX_APBIF_TXDIF2 29
#define APBIF_RX_I2S0_TXDIF 27
#define APBIF_RX_I2S1_TXDIF 26
#define APBIF_RX_I2S2_TXDIF 25
#define APBIF_RX_I2S3_TXDIF 23
#define APBIF_RX_DAM0_TXDIF 19
#define APBIF_RX_DAM1_TXDIF 15
/* SUNXI_AHUB_I2S_CTL */
#define I2S_CTL_LOOP3 23
#define I2S_CTL_LOOP2 22
#define I2S_CTL_LOOP1 21
#define I2S_CTL_LOOP0 20
#define I2S_CTL_SDI3_EN 15
#define I2S_CTL_SDI2_EN 14
#define I2S_CTL_SDI1_EN 13
#define I2S_CTL_SDI0_EN 12
#define I2S_CTL_CLK_OUT 18
#define I2S_CTL_SDO3_EN 11
#define I2S_CTL_SDO2_EN 10
#define I2S_CTL_SDO1_EN 9
#define I2S_CTL_SDO0_EN 8
#define I2S_CTL_OUT_MUTE 6
#define I2S_CTL_MODE 4
#define I2S_CTL_TXEN 2
#define I2S_CTL_RXEN 1
#define I2S_CTL_GEN 0
/* SUNXI_AHUB_I2S_FMT0 */
#define I2S_FMT0_LRCK_WIDTH 30
#define I2S_FMT0_LRCK_POLARITY 19
#define I2S_FMT0_LRCK_PERIOD 8
#define I2S_FMT0_BCLK_POLARITY 7
#define I2S_FMT0_SR 4
#define I2S_FMT0_EDGE 3
#define I2S_FMT0_SW 0
/* SUNXI_AHUB_I2S_FMT1 */
#define I2S_FMT1_RX_LSB 7
#define I2S_FMT1_TX_LSB 6
#define I2S_FMT1_EXT 4
#define I2S_FMT1_RX_PDM 2
#define I2S_FMT1_TX_PDM 0
/* SUNXI_AHUB_I2S_CLKD */
#define I2S_CLKD_MCLK 8
#define I2S_CLKD_BCLKDIV 4
#define I2S_CLKD_MCLKDIV 0
/* SUNXI_AHUB_I2S_RXCONT */
#define I2S_RX_APBIF_TXDIF0 31
#define I2S_RX_APBIF_TXDIF1 30
#define I2S_RX_APBIF_TXDIF2 29
#define I2S_RX_I2S0_TXDIF 27
#define I2S_RX_I2S1_TXDIF 26
#define I2S_RX_I2S2_TXDIF 25
#define I2S_RX_I2S3_TXDIF 23
#define I2S_RX_DAM0_TXDIF 19
#define I2S_RX_DAM1_TXDIF 15
/* SUNXI_AHUB_I2S_CHCFG */
#define I2S_CHCFG_HIZ 9
#define I2S_CHCFG_TX_STATE 8
#define I2S_CHCFG_RX_CHANNUM 4
#define I2S_CHCFG_TX_CHANNUM 0
/* SUNXI_AHUB_I2S_IRQ_CTL */
#define I2S_IRQ_RXOV_EN 1
#define I2S_IRQ_TXUV_EN 0
/* SUNXI_AHUB_I2S_IRQ_STA */
#define I2S_IRQ_RXOV_PEND 1
#define I2S_IRQ_TXUV_PEND 0
/* SUNXI_AHUB_I2S_OUT_SLOT */
#define I2S_OUT_OFFSET 20
#define I2S_OUT_SLOT_NUM 16
#define I2S_OUT_SLOT_EN 0
/* SUNXI_AHUB_I2S_IN_SLOT */
#define I2S_IN_OFFSET 20
#define I2S_IN_SLOT_NUM 16
/* SUNXI_AHUB_DAM_CTL */
#define DAM_CTL_RX2_NUM 24
#define DAM_CTL_RX1_NUM 20
#define DAM_CTL_RX0_NUM 16
#define DAM_CTL_TX_NUM 8
#define DAM_CTL_RX2EN 6
#define DAM_CTL_RX1EN 5
#define DAM_CTL_RX0EN 4
#define DAM_CTL_TXEN 0
/* SUNXI_AHUB_DAM_RX##chan##_SRC */
#define DAM_RX_APBIF_TXDIF0 31
#define DAM_RX_APBIF_TXDIF1 30
#define DAM_RX_APBIF_TXDIF2 29
#define DAM_RX_I2S0_TXDIF 27
#define DAM_RX_I2S1_TXDIF 26
#define DAM_RX_I2S2_TXDIF 25
#define DAM_RX_I2S3_TXDIF 23
#define DAM_RX_DAM0_TXDIF 19
#define DAM_RX_DAM1_TXDIF 15
struct sunxi_ahub_mem_info {
char *dev_name;
struct resource *res;
void __iomem *membase;
struct resource *memregion;
struct regmap *regmap;
};
struct sunxi_ahub_clk_info {
struct clk *clk_pll;
struct clk *clk_pllx4;
struct clk *clk_module;
struct clk *clk_bus;
struct reset_control *clk_rst;
};
extern int snd_soc_sunxi_ahub_mem_get(struct sunxi_ahub_mem_info *mem_info);
extern int snd_soc_sunxi_ahub_clk_get(struct sunxi_ahub_clk_info *clk_info);
#endif /* __SND_SUNXI_AHUB_DAM_H */

View File

@ -0,0 +1,267 @@
/*
* sound\soc\sunxi\snd_sunxi_common.c
* (C) Copyright 2021-2025
* AllWinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* 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.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/regmap.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include "snd_sunxi_log.h"
#include "snd_sunxi_common.h"
#define HLOG "COMMON"
/* for regmap */
int snd_sunxi_mem_init(struct platform_device *pdev,
struct sunxi_mem_info *mem_info)
{
int ret = 0;
struct device_node *np = pdev->dev.of_node;
SND_LOG_DEBUG(HLOG, "\n");
ret = of_address_to_resource(np, 0, mem_info->res);
if (ret) {
SND_LOG_ERR(HLOG, "parse device node resource failed\n");
ret = -EINVAL;
goto err_of_addr_to_resource;
}
mem_info->memregion = devm_request_mem_region(&pdev->dev,
mem_info->res->start,
resource_size(mem_info->res),
mem_info->dev_name);
if (IS_ERR_OR_NULL(mem_info->memregion)) {
SND_LOG_ERR(HLOG, "memory region already claimed\n");
ret = -EBUSY;
goto err_devm_request_region;
}
mem_info->membase = devm_ioremap(&pdev->dev,
mem_info->memregion->start,
resource_size(mem_info->memregion));
if (IS_ERR_OR_NULL(mem_info->membase)) {
SND_LOG_ERR(HLOG, "ioremap failed\n");
ret = -EBUSY;
goto err_devm_ioremap;
}
mem_info->regmap = devm_regmap_init_mmio(&pdev->dev,
mem_info->membase,
mem_info->regmap_config);
if (IS_ERR_OR_NULL(mem_info->regmap)) {
SND_LOG_ERR(HLOG, "regmap init failed\n");
ret = -EINVAL;
goto err_devm_regmap_init;
}
return 0;
err_devm_regmap_init:
devm_iounmap(&pdev->dev, mem_info->membase);
err_devm_ioremap:
devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
resource_size(mem_info->memregion));
err_devm_request_region:
err_of_addr_to_resource:
return ret;
}
void snd_sunxi_mem_exit(struct platform_device *pdev,
struct sunxi_mem_info *mem_info)
{
SND_LOG_DEBUG(HLOG, "\n");
devm_iounmap(&pdev->dev, mem_info->membase);
devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
resource_size(mem_info->memregion));
}
/* for reg labels */
int snd_sunxi_save_reg(struct regmap *regmap, struct reg_label *reg_labels)
{
int i = 0;
SND_LOG_DEBUG(HLOG, "\n");
while (reg_labels[i].name != NULL) {
regmap_read(regmap,
reg_labels[i].address, &(reg_labels[i].value));
i++;
}
return i;
}
int snd_sunxi_echo_reg(struct regmap *regmap, struct reg_label *reg_labels)
{
int i = 0;
SND_LOG_DEBUG(HLOG, "\n");
while (reg_labels[i].name != NULL) {
regmap_write(regmap,
reg_labels[i].address, reg_labels[i].value);
i++;
}
return i;
}
/* for pa config */
struct pa_config *snd_sunxi_pa_pin_init(struct platform_device *pdev,
u32 *pa_pin_max)
{
int ret, i;
u32 pin_max;
u32 gpio_tmp;
u32 temp_val;
char str[20] = {0};
struct pa_config *pa_cfg;
struct device_node *np = pdev->dev.of_node;
SND_LOG_DEBUG(HLOG, "\n");
*pa_pin_max = 0;
ret = of_property_read_u32(np, "pa_pin_max", &temp_val);
if (ret < 0) {
SND_LOG_WARN(HLOG, "pa_pin_max get failed, default 0\n");
return NULL;
} else {
pin_max = temp_val;
}
pa_cfg = kzalloc(sizeof(struct pa_config) * pin_max, GFP_KERNEL);
if (!pa_cfg) {
SND_LOG_ERR(HLOG, "can't pa_config memory\n");
return NULL;
}
for (i = 0; i < pin_max; i++) {
sprintf(str, "pa_pin_%d", i);
ret = of_get_named_gpio(np, str, 0);
if (ret < 0) {
SND_LOG_ERR(HLOG, "pa_pin_%u get failed\n", i);
pa_cfg[i].used = 0;
continue;
}
gpio_tmp = ret;
if (!gpio_is_valid(gpio_tmp)) {
SND_LOG_ERR(HLOG, "pa_pin_%u (%u) is invalid\n",
i, gpio_tmp);
pa_cfg[i].used = 0;
continue;
}
ret = devm_gpio_request(&pdev->dev, gpio_tmp, str);
if (ret) {
SND_LOG_ERR(HLOG, "pa_pin_%u (%u) request failed\n",
i, gpio_tmp);
pa_cfg[i].used = 0;
continue;
}
pa_cfg[i].used = 1;
pa_cfg[i].pin = gpio_tmp;
sprintf(str, "pa_pin_level_%d", i);
ret = of_property_read_u32(np, str, &temp_val);
if (ret < 0) {
SND_LOG_WARN(HLOG, "%s get failed, default low\n", str);
pa_cfg[i].level = 0;
} else {
if (temp_val > 0)
pa_cfg[i].level = 1;
}
sprintf(str, "pa_pin_msleep_%d", i);
ret = of_property_read_u32(np, str, &temp_val);
if (ret < 0) {
SND_LOG_WARN(HLOG, "%s get failed, default 0\n", str);
pa_cfg[i].msleep = 0;
} else {
pa_cfg[i].msleep = temp_val;
}
}
*pa_pin_max = pin_max;
snd_sunxi_pa_pin_disable(pa_cfg, pin_max);
return pa_cfg;
}
void snd_sunxi_pa_pin_exit(struct platform_device *pdev,
struct pa_config *pa_cfg, u32 pa_pin_max)
{
int i;
SND_LOG_DEBUG(HLOG, "\n");
snd_sunxi_pa_pin_disable(pa_cfg, pa_pin_max);
for (i = 0; i < pa_pin_max; i++) {
if (!pa_cfg[i].used)
continue;
gpio_free(pa_cfg[i].pin);
}
if (pa_cfg)
kfree(pa_cfg);
}
int snd_sunxi_pa_pin_enable(struct pa_config *pa_cfg, u32 pa_pin_max)
{
int i;
SND_LOG_DEBUG(HLOG, "\n");
if (pa_pin_max < 1) {
SND_LOG_DEBUG(HLOG, "no pa pin config\n");
return 0;
}
for (i = 0; i < pa_pin_max; i++) {
if (!pa_cfg[i].used)
continue;
gpio_direction_output(pa_cfg[i].pin, 1);
gpio_set_value(pa_cfg[i].pin, pa_cfg[i].level);
}
return 0;
}
void snd_sunxi_pa_pin_disable(struct pa_config *pa_cfg, u32 pa_pin_max)
{
int i;
SND_LOG_DEBUG(HLOG, "\n");
if (pa_pin_max < 1) {
SND_LOG_DEBUG(HLOG, "no pa pin config\n");
return;
}
for (i = 0; i < pa_pin_max; i++) {
if (!pa_cfg[i].used)
continue;
gpio_direction_output(pa_cfg[i].pin, 1);
gpio_set_value(pa_cfg[i].pin, !pa_cfg[i].level);
}
}

View File

@ -0,0 +1,67 @@
/* sound\soc\sunxi\snd_sunxi_common.h
* (C) Copyright 2021-2025
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* 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.
*/
#ifndef __SND_SUNXI_COMMON_H
#define __SND_SUNXI_COMMON_H
/* for regmap */
struct sunxi_mem_info {
char *dev_name;
struct resource *res;
struct regmap_config *regmap_config;
void __iomem *membase;
struct resource *memregion;
struct regmap *regmap;
};
int snd_sunxi_mem_init(struct platform_device *pdev,
struct sunxi_mem_info *mem_info);
void snd_sunxi_mem_exit(struct platform_device *pdev,
struct sunxi_mem_info *mem_info);
/* for reg debug */
#define REG_LABEL(constant) {#constant, constant, 0}
#define REG_LABEL_END {NULL, 0, 0}
struct reg_label {
const char *name;
const unsigned int address;
unsigned int value;
};
/* EX:
* static struct reg_label reg_labels[] = {
* REG_LABEL(SUNXI_REG_0),
* REG_LABEL(SUNXI_REG_1),
* REG_LABEL(SUNXI_REG_n),
* REG_LABEL_END,
* };
*/
int snd_sunxi_save_reg(struct regmap *regmap, struct reg_label *reg_labels);
int snd_sunxi_echo_reg(struct regmap *regmap, struct reg_label *reg_labels);
/* for pa config */
struct pa_config {
u32 pin;
u32 msleep;
bool used;
bool level;
};
struct pa_config *snd_sunxi_pa_pin_init(struct platform_device *pdev,
u32 *pa_pin_max);
void snd_sunxi_pa_pin_exit(struct platform_device *pdev,
struct pa_config *pa_cfg, u32 pa_pin_max);
int snd_sunxi_pa_pin_enable(struct pa_config *pa_cfg, u32 pa_pin_max);
void snd_sunxi_pa_pin_disable(struct pa_config *pa_cfg, u32 pa_pin_max);
#endif /* __SND_SUNXI_COMMON_H */

View File

@ -0,0 +1,30 @@
/*
* sound\soc\sunxi\snd_sunxi_log.h
* (C) Copyright 2021-2025
* allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* 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.
*/
#ifndef __SND_SUNXI_LOG_H
#define __SND_SUNXI_LOG_H
#include <linux/kernel.h>
#define SND_LOG_ERR(head, fmt, arg...) \
pr_err("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
#define SND_LOG_WARN(head, fmt, arg...) \
pr_warn("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
#define SND_LOG_INFO(head, fmt, arg...) \
pr_info("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
#define SND_LOG_DEBUG(head, fmt, arg...) \
pr_debug("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
#endif /* __SND_SUNXI_LOG_H */

View File

@ -0,0 +1,479 @@
/*
* sound\soc\sunxi\snd_sunxi_mach.c
* (C) Copyright 2021-2025
* AllWinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* based on ${LINUX}/sound/soc/generic/simple-card.c
*
* 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.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include "snd_sunxi_log.h"
#include "snd_sunxi_mach.h"
#define HLOG "MACH"
#define DAI "sound-dai"
#define CELL "#sound-dai-cells"
#define PREFIX "soundcard-mach,"
#define DRV_NAME "sunxi-snd-mach"
static void asoc_simple_shutdown(struct snd_pcm_substream *substream)
{
}
static int asoc_simple_startup(struct snd_pcm_substream *substream)
{
return 0;
}
static int asoc_simple_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, rtd->num);
struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num);
struct asoc_simple_dai *dais = priv->dais;
unsigned int mclk;
unsigned int cpu_pll_clk, codec_pll_clk;
unsigned int cpu_bclk_ratio, codec_bclk_ratio;
unsigned int freq_point;
int cpu_clk_div, codec_clk_div;
int ret = 0;
switch (params_rate(params)) {
case 8000:
case 12000:
case 16000:
case 24000:
case 32000:
case 48000:
case 64000:
case 96000:
case 192000:
freq_point = 24576000;
break;
case 11025:
case 22050:
case 44100:
case 88200:
case 176400:
freq_point = 22579200;
break;
default:
SND_LOG_ERR(HLOG, "Invalid rate %d\n", params_rate(params));
return -EINVAL;
}
/* for cpudai pll clk */
cpu_pll_clk = freq_point * dai_props->cpu_pll_fs;
codec_pll_clk = freq_point * dai_props->codec_pll_fs;
cpu_clk_div = cpu_pll_clk / params_rate(params);
codec_clk_div = codec_pll_clk / params_rate(params);
SND_LOG_DEBUG(HLOG, "freq point : %u\n", freq_point);
SND_LOG_DEBUG(HLOG, "cpu pllclk : %u\n", cpu_pll_clk);
SND_LOG_DEBUG(HLOG, "codec pllclk : %u\n", codec_pll_clk);
SND_LOG_DEBUG(HLOG, "cpu clk_div : %u\n", cpu_clk_div);
SND_LOG_DEBUG(HLOG, "codec clk_div: %u\n", codec_clk_div);
if (cpu_dai->driver->ops->set_pll) {
ret = snd_soc_dai_set_pll(cpu_dai, substream->stream, 0,
cpu_pll_clk, cpu_pll_clk);
if (ret) {
SND_LOG_ERR(HLOG, "cpu_dai set pllclk failed\n");
return ret;
}
}
if (codec_dai->driver->ops->set_pll) {
ret = snd_soc_dai_set_pll(codec_dai, substream->stream, 0,
codec_pll_clk, codec_pll_clk);
if (ret) {
SND_LOG_ERR(HLOG, "codec_dai set pllclk failed\n");
return ret;
}
}
if (cpu_dai->driver->ops->set_clkdiv) {
ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, cpu_clk_div);
if (ret) {
SND_LOG_ERR(HLOG, "cpu_dai set clk_div failed\n");
return ret;
}
}
if (codec_dai->driver->ops->set_clkdiv) {
ret = snd_soc_dai_set_clkdiv(codec_dai, 0, codec_clk_div);
if (ret) {
SND_LOG_ERR(HLOG, "cadec_dai set clk_div failed.\n");
return ret;
}
}
/* use for tdm only */
if (!(dais->slots && dais->slot_width))
return 0;
/* for cpudai & codecdai mclk */
if (dai_props->mclk_fp)
mclk = (freq_point >> 1) * dai_props->mclk_fs;
else
mclk = params_rate(params) * dai_props->mclk_fs;
cpu_bclk_ratio = cpu_pll_clk / (params_rate(params) * dais->slot_width * dais->slots);
codec_bclk_ratio = codec_pll_clk / (params_rate(params) * dais->slot_width * dais->slots);
SND_LOG_DEBUG(HLOG, "mclk : %u\n", mclk);
SND_LOG_DEBUG(HLOG, "cpu_bclk_ratio : %u\n", cpu_bclk_ratio);
SND_LOG_DEBUG(HLOG, "codec_bclk_ratio: %u\n", codec_bclk_ratio);
if (cpu_dai->driver->ops->set_sysclk) {
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT);
if (ret) {
SND_LOG_ERR(HLOG, "cpu_dai set sysclk(mclk) failed\n");
return ret;
}
}
if (codec_dai->driver->ops->set_sysclk) {
ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN);
if (ret) {
SND_LOG_ERR(HLOG, "cadec_dai set sysclk(mclk) failed\n");
return ret;
}
}
if (cpu_dai->driver->ops->set_bclk_ratio) {
ret = snd_soc_dai_set_bclk_ratio(cpu_dai, cpu_bclk_ratio);
if (ret) {
SND_LOG_ERR(HLOG, "cpu_dai set bclk failed\n");
return ret;
}
}
if (codec_dai->driver->ops->set_bclk_ratio) {
ret = snd_soc_dai_set_bclk_ratio(codec_dai, codec_bclk_ratio);
if (ret) {
SND_LOG_ERR(HLOG, "codec_dai set bclk failed\n");
return ret;
}
}
if (cpu_dai->driver->ops->set_fmt) {
ret = snd_soc_dai_set_fmt(cpu_dai, dai_link->dai_fmt);
if (ret) {
SND_LOG_ERR(HLOG, "cpu dai set fmt failed\n");
return ret;
}
}
if (codec_dai->driver->ops->set_fmt) {
ret = snd_soc_dai_set_fmt(codec_dai, dai_link->dai_fmt);
if (ret) {
SND_LOG_ERR(HLOG, "codec dai set fmt failed\n");
return ret;
}
}
if (cpu_dai->driver->ops->set_tdm_slot) {
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, dais->slots, dais->slot_width);
if (ret) {
SND_LOG_ERR(HLOG, "cpu dai set tdm slot failed\n");
return ret;
}
}
if (codec_dai->driver->ops->set_tdm_slot) {
ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, dais->slots, dais->slot_width);
if (ret) {
SND_LOG_ERR(HLOG, "codec dai set tdm slot failed\n");
return ret;
}
}
return 0;
}
static struct snd_soc_ops simple_ops = {
.startup = asoc_simple_startup,
.shutdown = asoc_simple_shutdown,
.hw_params = asoc_simple_hw_params,
};
static int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd)
{
int i;
struct snd_soc_card *card = rtd->card;
struct snd_soc_dapm_context *dapm = &card->dapm;
const struct snd_kcontrol_new *controls = card->controls;
for (i = 0; i < card->num_controls; i++)
if (controls[i].info == snd_soc_dapm_info_pin_switch)
snd_soc_dapm_disable_pin(dapm,
(const char *)controls[i].private_value);
if (card->num_controls)
snd_soc_dapm_sync(dapm);
/* snd_soc_dai_set_sysclk(); */
/* snd_soc_dai_set_tdm_slot(); */
return 0;
}
static int simple_dai_link_of(struct device_node *node,
struct asoc_simple_priv *priv)
{
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, 0);
struct simple_dai_props *dai_props = simple_priv_to_props(priv, 0);
struct device_node *top_np = NULL;
struct device_node *cpu = NULL;
struct device_node *plat = NULL;
struct device_node *codec = NULL;
char prop[128];
char *prefix = "";
int ret, single_cpu;
prefix = PREFIX;
top_np = node;
snprintf(prop, sizeof(prop), "%scpu", prefix);
cpu = of_get_child_by_name(top_np, prop);
if (!cpu) {
ret = -EINVAL;
SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop);
goto dai_link_of_err;
}
snprintf(prop, sizeof(prop), "%splat", prefix);
plat = of_get_child_by_name(top_np, prop);
snprintf(prop, sizeof(prop), "%scodec", prefix);
codec = of_get_child_by_name(top_np, prop);
if (!codec) {
ret = -EINVAL;
SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop);
goto dai_link_of_err;
}
ret = asoc_simple_parse_daifmt(top_np, codec, prefix, &dai_link->dai_fmt);
if (ret < 0)
goto dai_link_of_err;
/* sunxi: parse stream direction
* ex1)
* top_node {
* PREFIXplayback-only;
* }
* ex2)
* top_node {
* PREFIXcapture-only;
* }
*/
ret = asoc_simple_parse_daistream(top_np, prefix, dai_link);
if (ret < 0)
goto dai_link_of_err;
/* sunxi: parse slot-num & slot-width
* ex)
* top_node {
* PREFIXplayslot-num = <x>;
* PREFIXplayslot-width = <x>;
* }
*/
ret = asoc_simple_parse_tdm_slot(top_np, prefix, priv->dais);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_parse_cpu(cpu, dai_link, DAI, CELL, &single_cpu);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_parse_codec(codec, dai_link, DAI, CELL);
if (ret < 0) {
if (ret == -EPROBE_DEFER)
goto dai_link_of_err;
dai_link->codecs->name = "snd-soc-dummy";
dai_link->codecs->dai_name = "snd-soc-dummy-dai";
/* dai_link->codecs->name = "sunxi-dummy-codec"; */
/* dai_link->codecs->dai_name = "sunxi-dummy-codec-dai"; */
SND_LOG_DEBUG(HLOG, "use dummy codec for simple card.\n");
}
ret = asoc_simple_parse_platform(plat, dai_link, DAI, CELL);
if (ret < 0)
goto dai_link_of_err;
/* sunxi: parse pll-fs & mclk-fs
* ex)
* top_node {
* PREFIXcpu {
* PREFIXpll-fs = <x>;
* PREFIXmclk-fs = <x>;
* }
* }
*/
ret = asoc_simple_parse_tdm_clk(cpu, codec, prefix, dai_props);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_set_dailink_name(dev, dai_link,
"%s-%s",
dai_link->cpus->dai_name,
dai_link->codecs->dai_name);
if (ret < 0)
goto dai_link_of_err;
dai_link->ops = &simple_ops;
dai_link->init = asoc_simple_dai_init;
SND_LOG_DEBUG(HLOG, "name : %s\n", dai_link->stream_name);
SND_LOG_DEBUG(HLOG, "format : %x\n", dai_link->dai_fmt);
SND_LOG_DEBUG(HLOG, "cpu : %s\n", dai_link->cpus->name);
SND_LOG_DEBUG(HLOG, "codec : %s\n", dai_link->codecs->name);
asoc_simple_canonicalize_cpu(dai_link, single_cpu);
asoc_simple_canonicalize_platform(dai_link);
dai_link_of_err:
of_node_put(cpu);
of_node_put(plat);
of_node_put(codec);
return ret;
}
static int simple_parse_of(struct asoc_simple_priv *priv)
{
int ret;
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_card *card = simple_priv_to_card(priv);
struct device_node *top_np = dev->of_node;
SND_LOG_DEBUG(HLOG, "\n");
if (!top_np)
return -EINVAL;
/* DAPM widgets */
ret = asoc_simple_parse_widgets(card, PREFIX);
if (ret < 0)
return ret;
/* DAPM routes */
ret = asoc_simple_parse_routing(card, PREFIX);
if (ret < 0)
return ret;
/* DAPM pin_switches */
ret = asoc_simple_parse_pin_switches(card, PREFIX);
if (ret < 0)
return ret;
/* For single DAI link & old style of DT node */
ret = simple_dai_link_of(top_np, priv);
if (ret < 0)
return ret;
ret = asoc_simple_parse_card_name(card, PREFIX);
return ret;
}
static int simple_soc_probe(struct snd_soc_card *card)
{
return 0;
}
static int asoc_simple_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *top_np = dev->of_node;
struct asoc_simple_priv *priv;
struct snd_soc_card *card;
int ret;
/* Allocate the private data and the DAI link array */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card = simple_priv_to_card(priv);
card->owner = THIS_MODULE;
card->dev = dev;
card->probe = simple_soc_probe;
ret = asoc_simple_init_priv(priv);
if (ret < 0)
return ret;
if (top_np && of_device_is_available(top_np)) {
ret = simple_parse_of(priv);
if (ret < 0) {
if (ret != -EPROBE_DEFER)
SND_LOG_ERR(HLOG, "parse error %d\n", ret);
goto err;
}
} else {
SND_LOG_ERR(HLOG, "simple card dts available\n");
}
snd_soc_card_set_drvdata(card, priv);
/* asoc_simple_debug_info(priv); */
ret = devm_snd_soc_register_card(dev, card);
if (ret >= 0)
return ret;
err:
asoc_simple_clean_reference(card);
return ret;
}
static int asoc_simple_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
return asoc_simple_clean_reference(card);
}
static const struct of_device_id snd_soc_sunxi_of_match[] = {
{ .compatible = "allwinner," DRV_NAME, },
{},
};
MODULE_DEVICE_TABLE(of, snd_soc_sunxi_of_match);
static struct platform_driver sunxi_soundcard_machine_driver = {
.driver = {
.name = DRV_NAME,
.pm = &snd_soc_pm_ops,
.of_match_table = snd_soc_sunxi_of_match,
},
.probe = asoc_simple_probe,
.remove = asoc_simple_remove,
};
int __init sunxi_soundcard_machine_dev_init(void)
{
int ret;
ret = platform_driver_register(&sunxi_soundcard_machine_driver);
if (ret != 0) {
SND_LOG_ERR(HLOG, "platform driver register failed\n");
return -EINVAL;
}
return ret;
}
void __exit sunxi_soundcard_machine_dev_exit(void)
{
platform_driver_unregister(&sunxi_soundcard_machine_driver);
}
late_initcall(sunxi_soundcard_machine_dev_init);
module_exit(sunxi_soundcard_machine_dev_exit);
MODULE_AUTHOR("Dby@allwinnertech.com");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("sunxi soundcard machine");

View File

@ -0,0 +1,17 @@
/* sound\soc\sunxi\snd_sunxi_mach.h
* (C) Copyright 2021-2025
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* 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.
*/
#ifndef __SND_SUNXI_MACH_H
#define __SND_SUNXI_MACH_H
#include "snd_sunxi_mach_utils.h"
#endif /* __SND_SUNXI_MACH_H */

View File

@ -0,0 +1,422 @@
/*
* sound\soc\sunxi\snd_sunxi_mach_utils.c
* (C) Copyright 2021-2025
* AllWinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* based on ${LINUX}/sound/soc/generic/simple-card.c
*
* 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.
*/
#include <linux/module.h>
#include <linux/of.h>
#include <sound/soc.h>
#include "snd_sunxi_log.h"
#include "snd_sunxi_mach_utils.h"
#define HLOG "mach_utils"
int asoc_simple_clean_reference(struct snd_soc_card *card)
{
struct snd_soc_dai_link *dai_link;
int i;
for_each_card_prelinks(card, i, dai_link) {
of_node_put(dai_link->cpus->of_node);
of_node_put(dai_link->codecs->of_node);
}
return 0;
}
int asoc_simple_init_priv(struct asoc_simple_priv *priv)
{
struct snd_soc_card *card = simple_priv_to_card(priv);
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_dai_link *dai_link;
struct simple_dai_props *dai_props;
struct asoc_simple_dai *dais;
struct snd_soc_codec_conf *cconf = NULL;
dai_props = devm_kcalloc(dev, 1, sizeof(*dai_props), GFP_KERNEL);
dai_link = devm_kcalloc(dev, 1, sizeof(*dai_link), GFP_KERNEL);
dais = devm_kcalloc(dev, 1, sizeof(*dais), GFP_KERNEL);
if (!dai_props || !dai_link || !dais)
return -ENOMEM;
/*
if (li->conf) {
cconf = devm_kcalloc(dev, li->conf, sizeof(*cconf), GFP_KERNEL);
if (!cconf)
return -ENOMEM;
}
*/
/*
* Use snd_soc_dai_link_component instead of legacy style
* It is codec only. but cpu/platform will be supported in the future.
* see
* soc-core.c :: snd_soc_init_multicodec()
*
* "platform" might be removed
* see
* simple-card-utils.c :: asoc_simple_canonicalize_platform()
*/
dai_link->cpus = &dai_props->cpus;
dai_link->num_cpus = 1;
dai_link->codecs = &dai_props->codecs;
dai_link->num_codecs = 1;
dai_link->platforms = &dai_props->platforms;
dai_link->num_platforms = 1;
priv->dai_props = dai_props;
priv->dai_link = dai_link;
priv->dais = dais;
priv->codec_conf = cconf;
card->dai_link = priv->dai_link;
card->num_links = 1;
card->codec_conf = cconf;
card->num_configs = 0;
return 0;
}
int asoc_simple_parse_widgets(struct snd_soc_card *card, char *prefix)
{
struct device_node *node = card->dev->of_node;
char prop[128];
if (!prefix)
prefix = "";
snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets");
if (of_property_read_bool(node, prop))
return snd_soc_of_parse_audio_simple_widgets(card, prop);
/* no widgets is not error */
return 0;
}
int asoc_simple_parse_routing(struct snd_soc_card *card, char *prefix)
{
struct device_node *node = card->dev->of_node;
char prop[128];
if (!prefix)
prefix = "";
snprintf(prop, sizeof(prop), "%s%s", prefix, "routing");
if (!of_property_read_bool(node, prop))
return 0;
return snd_soc_of_parse_audio_routing(card, prop);
}
int asoc_simple_parse_pin_switches(struct snd_soc_card *card, char *prefix)
{
const unsigned int nb_controls_max = 16;
const char **strings, *control_name;
struct snd_kcontrol_new *controls;
struct device *dev = card->dev;
unsigned int i, nb_controls;
char prop[128];
int ret;
if (!prefix)
prefix = "";
snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches");
if (!of_property_read_bool(dev->of_node, prop))
return 0;
strings = devm_kcalloc(dev, nb_controls_max,
sizeof(*strings), GFP_KERNEL);
if (!strings)
return -ENOMEM;
ret = of_property_read_string_array(dev->of_node, prop,
strings, nb_controls_max);
if (ret < 0)
return ret;
nb_controls = (unsigned int)ret;
controls = devm_kcalloc(dev, nb_controls,
sizeof(*controls), GFP_KERNEL);
if (!controls)
return -ENOMEM;
for (i = 0; i < nb_controls; i++) {
control_name = devm_kasprintf(dev, GFP_KERNEL,
"%s Switch", strings[i]);
if (!control_name)
return -ENOMEM;
controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
controls[i].name = control_name;
controls[i].info = snd_soc_dapm_info_pin_switch;
controls[i].get = snd_soc_dapm_get_pin_switch;
controls[i].put = snd_soc_dapm_put_pin_switch;
controls[i].private_value = (unsigned long)strings[i];
}
card->controls = controls;
card->num_controls = nb_controls;
return 0;
}
int asoc_simple_parse_daifmt(struct device_node *node,
struct device_node *codec,
char *prefix,
unsigned int *retfmt)
{
struct device_node *bitclkmaster = NULL;
struct device_node *framemaster = NULL;
unsigned int daifmt;
daifmt = snd_soc_daifmt_parse_format(node, prefix);
snd_soc_daifmt_parse_clock_provider_as_phandle(node, prefix, &bitclkmaster, &framemaster);
if (!bitclkmaster && !framemaster) {
/*
* No dai-link level and master setting was not found from
* sound node level, revert back to legacy DT parsing and
* take the settings from codec node.
*/
SND_LOG_DEBUG(HLOG, "Revert to legacy daifmt parsing\n");
daifmt |= snd_soc_daifmt_parse_clock_provider_as_flag(codec, NULL);
} else {
daifmt |= snd_soc_daifmt_clock_provider_from_bitmap(
((codec == bitclkmaster) << 4) | (codec == framemaster));
}
of_node_put(bitclkmaster);
of_node_put(framemaster);
*retfmt = daifmt;
return 0;
}
int asoc_simple_parse_daistream(struct device_node *node, char *prefix,
struct snd_soc_dai_link *dai_link)
{
char prop[128];
if (!prefix)
prefix = "";
/* check "[prefix]playback-only" */
snprintf(prop, sizeof(prop), "%splayback-only", prefix);
if (of_property_read_bool(node, prop))
dai_link->playback_only = 1;
/* check "[prefix]capture-only" */
snprintf(prop, sizeof(prop), "%scapture-only", prefix);
if (of_property_read_bool(node, prop))
dai_link->capture_only = 1;
return 0;
}
int asoc_simple_parse_tdm_slot(struct device_node *node, char *prefix,
struct asoc_simple_dai *dais)
{
int ret;
char prop[128];
unsigned int val;
if (!prefix)
prefix = "";
snprintf(prop, sizeof(prop), "%sslot-num", prefix);
ret = of_property_read_u32(node, prop, &val);
if (!ret)
dais->slots = val;
snprintf(prop, sizeof(prop), "%sslot-width", prefix);
ret = of_property_read_u32(node, prop, &val);
if (!ret)
dais->slot_width = val;
return 0;
}
int asoc_simple_parse_tdm_clk(struct device_node *cpu,
struct device_node *codec,
char *prefix,
struct simple_dai_props *dai_props)
{
int ret;
char prop[128];
unsigned int val;
if (!prefix)
prefix = "";
snprintf(prop, sizeof(prop), "%spll-fs", prefix);
ret = of_property_read_u32(cpu, prop, &val);
if (ret)
dai_props->cpu_pll_fs = 1; /* default sysclk 24.576 or 22.5792MHz * 1 */
else
dai_props->cpu_pll_fs = val;
ret = of_property_read_u32(codec, prop, &val);
if (ret)
dai_props->codec_pll_fs = 1; /* default sysclk 24.576 or 22.5792MHz * 1 */
else
dai_props->codec_pll_fs = val;
snprintf(prop, sizeof(prop), "%smclk-fp", prefix);
dai_props->mclk_fp = of_property_read_bool(cpu, prop);
snprintf(prop, sizeof(prop), "%smclk-fs", prefix);
ret = of_property_read_u32(cpu, prop, &val);
if (ret)
dai_props->mclk_fs = 0; /* default mclk 0Hz(un output) */
else
dai_props->mclk_fs = val;
return 0;
}
int asoc_simple_parse_card_name(struct snd_soc_card *card,
char *prefix)
{
int ret;
if (!prefix)
prefix = "";
/* Parse the card name from DT */
ret = snd_soc_of_parse_card_name(card, "label");
if (ret < 0 || !card->name) {
char prop[128];
snprintf(prop, sizeof(prop), "%sname", prefix);
ret = snd_soc_of_parse_card_name(card, prop);
if (ret < 0)
return ret;
}
if (!card->name && card->dai_link)
card->name = card->dai_link->name;
return 0;
}
int asoc_simple_parse_dai(struct device_node *node,
struct snd_soc_dai_link_component *dlc,
const char *list_name, const char *cells_name,
int *is_single_link)
{
struct of_phandle_args args;
int ret;
if (!node)
return 0;
/*
* Get node via "sound-dai = <&phandle port>"
* it will be used as xxx_of_node on soc_bind_dai_link()
*/
ret = of_parse_phandle_with_args(node, list_name, cells_name, 0, &args);
if (ret)
return ret;
/*
* FIXME
*
* Here, dlc->dai_name is pointer to CPU/Codec DAI name.
* If user unbinded CPU or Codec driver, but not for Sound Card,
* dlc->dai_name is keeping unbinded CPU or Codec
* driver's pointer.
*
* If user re-bind CPU or Codec driver again, ALSA SoC will try
* to rebind Card via snd_soc_try_rebind_card(), but because of
* above reason, it might can't bind Sound Card.
* Because Sound Card is pointing to released dai_name pointer.
*
* To avoid this rebind Card issue,
* 1) It needs to alloc memory to keep dai_name eventhough
* CPU or Codec driver was unbinded, or
* 2) user need to rebind Sound Card everytime
* if he unbinded CPU or Codec.
*/
ret = snd_soc_of_get_dai_name(node, &dlc->dai_name);
if (ret < 0)
return ret;
dlc->of_node = args.np;
if (is_single_link)
*is_single_link = !args.args_count;
return 0;
}
int asoc_simple_set_dailink_name(struct device *dev,
struct snd_soc_dai_link *dai_link,
const char *fmt, ...)
{
va_list ap;
char *name = NULL;
int ret = -ENOMEM;
va_start(ap, fmt);
name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
va_end(ap);
if (name) {
ret = 0;
dai_link->name = name;
dai_link->stream_name = name;
}
return ret;
}
void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link)
{
/* Assumes platform == cpu */
if (!dai_link->platforms->of_node)
dai_link->platforms->of_node = dai_link->cpus->of_node;
/*
* DPCM BE can be no platform.
* Alloced memory will be waste, but not leak.
*/
if (!dai_link->platforms->of_node)
dai_link->num_platforms = 0;
}
void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link,
int is_single_links)
{
/*
* In soc_bind_dai_link() will check cpu name after
* of_node matching if dai_link has cpu_dai_name.
* but, it will never match if name was created by
* fmt_single_name() remove cpu_dai_name if cpu_args
* was 0. See:
* fmt_single_name()
* fmt_multiple_name()
*/
if (is_single_links)
dai_link->cpus->dai_name = NULL;
}
MODULE_AUTHOR("Dby@allwinnertech.com");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("sunxi soundcard machine utils");

View File

@ -0,0 +1,116 @@
/* sound\soc\sunxi\snd_sunxi_mach_utils.h
* (C) Copyright 2021-2025
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Dby <dby@allwinnertech.com>
*
* 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.
*/
#ifndef __SND_SUNXI_MACH_UTILS_H
#define __SND_SUNXI_MACH_UTILS_H
#define simple_priv_to_card(priv) (&(priv)->snd_card)
#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i))
#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev)
#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i))
#define asoc_simple_parse_cpu(node, dai_link, \
list_name, cells_name, is_single_link) \
asoc_simple_parse_dai(node, dai_link->cpus, \
list_name, cells_name, is_single_link)
#define asoc_simple_parse_codec(node, dai_link, \
list_name, cells_name) \
asoc_simple_parse_dai(node, dai_link->codecs, \
list_name, cells_name, NULL)
#define asoc_simple_parse_platform(node, dai_link, \
list_name, cells_name) \
asoc_simple_parse_dai(node, dai_link->platforms, \
list_name, cells_name, NULL)
struct asoc_simple_dai {
const char *name;
unsigned int sysclk;
int clk_direction;
int slots;
int slot_width;
unsigned int tx_slot_mask;
unsigned int rx_slot_mask;
struct clk *clk;
};
struct asoc_simple_data {
u32 convert_rate;
u32 convert_channels;
};
struct asoc_simple_jack {
struct snd_soc_jack jack;
struct snd_soc_jack_pin pin;
struct snd_soc_jack_gpio gpio;
};
struct asoc_simple_priv {
struct snd_soc_card snd_card;
struct simple_dai_props {
struct asoc_simple_dai *cpu_dai;
struct asoc_simple_dai *codec_dai;
struct snd_soc_dai_link_component cpus; /* single cpu */
struct snd_soc_dai_link_component codecs; /* single codec */
struct snd_soc_dai_link_component platforms;
struct asoc_simple_data adata;
struct snd_soc_codec_conf *codec_conf;
bool mclk_fp;
unsigned int mclk_fs;
unsigned int cpu_pll_fs;
unsigned int codec_pll_fs;
} *dai_props;
struct asoc_simple_jack hp_jack;
struct asoc_simple_jack mic_jack;
struct snd_soc_dai_link *dai_link;
struct asoc_simple_dai *dais;
struct snd_soc_codec_conf *codec_conf;
struct gpio_desc *pa_gpio;
};
int asoc_simple_clean_reference(struct snd_soc_card *card);
int asoc_simple_init_priv(struct asoc_simple_priv *priv);
int asoc_simple_parse_widgets(struct snd_soc_card *card, char *prefix);
int asoc_simple_parse_routing(struct snd_soc_card *card, char *prefix);
int asoc_simple_parse_pin_switches(struct snd_soc_card *card, char *prefix);
int asoc_simple_parse_daistream(struct device_node *node,
char *prefix,
struct snd_soc_dai_link *dai_link);
int asoc_simple_parse_daifmt(struct device_node *node,
struct device_node *codec,
char *prefix,
unsigned int *retfmt);
int asoc_simple_parse_tdm_slot(struct device_node *node,
char *prefix,
struct asoc_simple_dai *dais);
int asoc_simple_parse_tdm_clk(struct device_node *cpu,
struct device_node *codec,
char *prefix,
struct simple_dai_props *dai_props);
int asoc_simple_parse_card_name(struct snd_soc_card *card, char *prefix);
int asoc_simple_parse_dai(struct device_node *node,
struct snd_soc_dai_link_component *dlc,
const char *list_name,
const char *cells_name,
int *is_single_link);
int asoc_simple_set_dailink_name(struct device *dev,
struct snd_soc_dai_link *dai_link,
const char *fmt, ...);
void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link);
void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link,
int is_single_links);
#endif /* __SND_SUNXI_MACH_UTILS_H */