mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-06 02:50:49 +09:00
ASoC: codecs: add rockchip-spi-codec driver support
Introduce a codec driver to control a remote audio dsp chip by spi. This patch is running on a rk3588 board with rk3308 adsp board, while the rk3308 actually is a SoC without a slave spi controller, so the rk3588 needs connect to rk3308 by spi. Currently, the host(rk3588) uses the audiocontrol to set volume to the adsp. Change-Id: Ia07e9def78cfe7441dc03c97124f0532bc9d72bc Signed-off-by: Jianqun Xu <jay.xu@rock-chips.com> Signed-off-by: Jun Zeng <jun.zeng@rock-chips.com>
This commit is contained in:
@@ -164,6 +164,7 @@ config SND_SOC_ALL_CODECS
|
||||
imply SND_SOC_RK3528
|
||||
imply SND_SOC_RK730
|
||||
imply SND_SOC_RK817
|
||||
imply SND_SOC_ROCKCHIP_SPI_CODEC
|
||||
imply SND_SOC_RT274
|
||||
imply SND_SOC_RT286
|
||||
imply SND_SOC_RT298
|
||||
@@ -1170,6 +1171,15 @@ config SND_SOC_RL6347A
|
||||
default m if SND_SOC_RT286=m
|
||||
default m if SND_SOC_RT298=m
|
||||
|
||||
config SND_SOC_ROCKCHIP_SPI_CODEC
|
||||
tristate "SPI CODEC"
|
||||
depends on ARCH_ROCKCHIP
|
||||
depends on SPI_MASTER
|
||||
select REGMAP_SPI
|
||||
help
|
||||
Enable support for a remote audio dsp which act as a codec.
|
||||
Control the adsp by spi.
|
||||
|
||||
config SND_SOC_RT274
|
||||
tristate
|
||||
depends on I2C
|
||||
|
||||
@@ -175,6 +175,7 @@ snd-soc-rk-codec-digital-objs := rk_codec_digital.o
|
||||
snd-soc-rk-dsm-objs := rk_dsm.o
|
||||
snd-soc-rl6231-objs := rl6231.o
|
||||
snd-soc-rl6347a-objs := rl6347a.o
|
||||
snd-soc-rockchip-spi-codec-objs := rockchip-spi-codec.o
|
||||
snd-soc-rt1011-objs := rt1011.o
|
||||
snd-soc-rt1015-objs := rt1015.o
|
||||
snd-soc-rt1015p-objs := rt1015p.o
|
||||
@@ -503,6 +504,7 @@ obj-$(CONFIG_SND_SOC_RK_CODEC_DIGITAL) += snd-soc-rk-codec-digital.o
|
||||
obj-$(CONFIG_SND_SOC_RK_DSM) += snd-soc-rk-dsm.o
|
||||
obj-$(CONFIG_SND_SOC_RL6231) += snd-soc-rl6231.o
|
||||
obj-$(CONFIG_SND_SOC_RL6347A) += snd-soc-rl6347a.o
|
||||
obj-$(CONFIG_SND_SOC_ROCKCHIP_SPI_CODEC) += snd-soc-rockchip-spi-codec.o
|
||||
obj-$(CONFIG_SND_SOC_RT1011) += snd-soc-rt1011.o
|
||||
obj-$(CONFIG_SND_SOC_RT1015) += snd-soc-rt1015.o
|
||||
obj-$(CONFIG_SND_SOC_RT1015P) += snd-soc-rt1015p.o
|
||||
|
||||
370
sound/soc/codecs/rockchip-spi-codec.c
Normal file
370
sound/soc/codecs/rockchip-spi-codec.c
Normal file
@@ -0,0 +1,370 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Rockchip Audio CODEC Driver for remote dsp
|
||||
*
|
||||
* Copyright (C) 2023 Rockchip Electronics Co.,Ltd
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "rockchip-spi-codec.h"
|
||||
|
||||
#define TDM_CH_MAX (32)
|
||||
#define TDM_CH_ID(n) (n)
|
||||
#define VOL_MAX (0x27)
|
||||
#define VOL_MIN (0x00)
|
||||
|
||||
struct spi_codec_private {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct spi_device *spi;
|
||||
struct snd_soc_component *component;
|
||||
struct spi_codec_protocol_packet *packet;
|
||||
struct mutex lock;
|
||||
struct gpio_desc *reset_gpio;
|
||||
int tdm_volume[TDM_CH_MAX];
|
||||
int tdm_mute[TDM_CH_MAX];
|
||||
};
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(playback_tlv, -1000, 100, 0);
|
||||
|
||||
#define SPICODEC_CH_VOLUME(name, ch) \
|
||||
SOC_SINGLE_EXT_TLV(name, ch, VOL_MIN, VOL_MAX, 0X00, \
|
||||
spi_codec_ext_ch_volume_get, \
|
||||
spi_codec_ext_ch_volume_put, playback_tlv)
|
||||
|
||||
#define SPICODEC_CH_MUTE(name, ch) \
|
||||
SOC_SINGLE_BOOL_EXT(name, ch, \
|
||||
spi_codec_ext_ch_mute_get, \
|
||||
spi_codec_ext_ch_mute_put)
|
||||
|
||||
static inline struct spi_device *soc_component_to_spi(struct snd_soc_component *c)
|
||||
{
|
||||
return container_of(c->dev, struct spi_device, dev);
|
||||
}
|
||||
|
||||
static int spi_codec_cmd_request_unlock(struct spi_codec_private *spi_priv,
|
||||
unsigned int cmd_type,
|
||||
unsigned int *payload,
|
||||
unsigned int payload_len,
|
||||
unsigned int address)
|
||||
{
|
||||
struct spi_device *spi = spi_priv->spi;
|
||||
unsigned int packet_size, num, loop;
|
||||
|
||||
if (!spi)
|
||||
return -ENODEV;
|
||||
|
||||
packet_size = sizeof(struct spi_codec_protocol_packet);
|
||||
|
||||
spi_priv->packet->cmd_begin = SS_CMD_BEGIN;
|
||||
spi_priv->packet->cmd_type = cmd_type;
|
||||
|
||||
loop = roundup(payload_len, PAYLOAD_MAX) / PAYLOAD_MAX;
|
||||
|
||||
spi_priv->packet->crc = 0;
|
||||
spi_priv->packet->address = address;
|
||||
|
||||
for (num = 0; num < loop; num++) {
|
||||
if (num == loop - 1) {
|
||||
spi_priv->packet->payload_len = payload_len % PAYLOAD_MAX;
|
||||
spi_priv->packet->cmd_end = SS_CMD_END;
|
||||
} else {
|
||||
spi_priv->packet->payload_len = PAYLOAD_MAX;
|
||||
spi_priv->packet->cmd_end = SS_CMD_PARTIAL_END;
|
||||
}
|
||||
|
||||
memcpy(spi_priv->packet->payload, payload + num * PAYLOAD_MAX,
|
||||
spi_priv->packet->payload_len);
|
||||
|
||||
if (spi_write(spi, spi_priv->packet, packet_size)) {
|
||||
dev_err(&spi->dev, "ERR:spi write failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_set_parameter(struct spi_codec_private *spi_priv,
|
||||
unsigned int cmd_type,
|
||||
unsigned int *payload,
|
||||
unsigned int payload_len,
|
||||
unsigned int address)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&spi_priv->lock);
|
||||
ret = spi_codec_cmd_request_unlock(spi_priv, cmd_type, payload,
|
||||
payload_len, address);
|
||||
mutex_unlock(&spi_priv->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spi_codec_ext_ch_volume_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
struct spi_codec_private *priv = snd_soc_component_get_drvdata(component);
|
||||
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int ch = mc->reg;
|
||||
|
||||
ucontrol->value.integer.value[0] = priv->tdm_volume[ch];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_ext_ch_volume_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
struct spi_codec_private *priv = snd_soc_component_get_drvdata(component);
|
||||
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int ch = mc->reg;
|
||||
|
||||
priv->tdm_volume[ch] = ucontrol->value.integer.value[0];
|
||||
|
||||
spi_codec_set_parameter(priv, SS_CMD_BLOCK_PARAMETER_SAFE,
|
||||
&priv->tdm_volume[ch], sizeof(priv->tdm_volume[ch]),
|
||||
TDM_CH_ID(ch));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_ext_ch_mute_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
struct spi_codec_private *priv = snd_soc_component_get_drvdata(component);
|
||||
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int ch = mc->reg;
|
||||
|
||||
ucontrol->value.integer.value[0] = priv->tdm_mute[ch];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_ext_ch_mute_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
||||
struct spi_codec_private *priv = snd_soc_component_get_drvdata(component);
|
||||
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int ch = mc->reg;
|
||||
|
||||
priv->tdm_mute[ch] = ucontrol->value.integer.value[0];
|
||||
|
||||
spi_codec_set_parameter(priv, SS_CMD_BLOCK_PARAMETER_SAFE,
|
||||
&priv->tdm_mute[ch], sizeof(priv->tdm_mute[ch]),
|
||||
TDM_CH_ID(TDM_CH_MAX + ch));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void spi_codec_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
}
|
||||
|
||||
static int spi_codec_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_hw_free(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_set_sysclk(struct snd_soc_dai *cpu_dai, int stream,
|
||||
unsigned int freq, int dir)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_set_fmt(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_codec_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd, struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops spi_codec_dai_ops = {
|
||||
.startup = spi_codec_startup,
|
||||
.shutdown = spi_codec_shutdown,
|
||||
.hw_params = spi_codec_hw_params,
|
||||
.hw_free = spi_codec_hw_free,
|
||||
.set_sysclk = spi_codec_set_sysclk,
|
||||
.set_fmt = spi_codec_set_fmt,
|
||||
.trigger = spi_codec_trigger,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver spi_codec_dai = {
|
||||
.name = "spi_codec",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 384,
|
||||
.rates = SNDRV_PCM_RATE_8000_384000,
|
||||
.formats = (SNDRV_PCM_FMTBIT_S8 |
|
||||
SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE),
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 384,
|
||||
.rates = SNDRV_PCM_RATE_8000_384000,
|
||||
.formats = (SNDRV_PCM_FMTBIT_S8 |
|
||||
SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE),
|
||||
},
|
||||
.ops = &spi_codec_dai_ops,
|
||||
};
|
||||
|
||||
static int spi_codec_comp_probe(struct snd_soc_component *component)
|
||||
{
|
||||
struct spi_device *spi = container_of(component->dev, struct spi_device, dev);
|
||||
struct spi_codec_private *spi_priv = dev_get_drvdata(&spi->dev);
|
||||
|
||||
snd_soc_component_set_drvdata(component, spi_priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void spi_codec_comp_remove(struct snd_soc_component *component)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget spi_codec_dapm_widgets[] = {
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route spi_codec_dapm_routes[] = {
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new spi_codec_snd_controls[] = {
|
||||
SPICODEC_CH_VOLUME("CHN0 Playback Vol", 0),
|
||||
SPICODEC_CH_VOLUME("CHN1 Playback Vol", 1),
|
||||
SPICODEC_CH_VOLUME("CHN2 Playback Vol", 2),
|
||||
SPICODEC_CH_VOLUME("CHN3 Playback Vol", 3),
|
||||
SPICODEC_CH_VOLUME("CHN4 Playback Vol", 4),
|
||||
SPICODEC_CH_VOLUME("CHN5 Playback Vol", 5),
|
||||
SPICODEC_CH_VOLUME("CHN6 Playback Vol", 6),
|
||||
SPICODEC_CH_VOLUME("CHN7 Playback Vol", 7),
|
||||
SPICODEC_CH_MUTE("CHN0 Playback Mute", 0),
|
||||
SPICODEC_CH_MUTE("CHN1 Playback Mute", 1),
|
||||
SPICODEC_CH_MUTE("CHN2 Playback Mute", 2),
|
||||
SPICODEC_CH_MUTE("CHN3 Playback Mute", 3),
|
||||
SPICODEC_CH_MUTE("CHN4 Playback Mute", 4),
|
||||
SPICODEC_CH_MUTE("CHN5 Playback Mute", 5),
|
||||
SPICODEC_CH_MUTE("CHN6 Playback Mute", 6),
|
||||
SPICODEC_CH_MUTE("CHN7 Playback Mute", 7),
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver spi_codec_component = {
|
||||
.probe = spi_codec_comp_probe,
|
||||
.remove = spi_codec_comp_remove,
|
||||
.dapm_widgets = spi_codec_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(spi_codec_dapm_widgets),
|
||||
.dapm_routes = spi_codec_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(spi_codec_dapm_routes),
|
||||
.controls = spi_codec_snd_controls,
|
||||
.num_controls = ARRAY_SIZE(spi_codec_snd_controls),
|
||||
};
|
||||
|
||||
static int spi_codec_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct spi_codec_private *spi_priv;
|
||||
int ret = 0;
|
||||
|
||||
spi_priv = devm_kzalloc(dev, sizeof(*spi_priv), GFP_KERNEL);
|
||||
if (!spi_priv)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_priv->packet = devm_kzalloc(dev, sizeof(*spi_priv->packet), GFP_KERNEL);
|
||||
if (!spi_priv->packet)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_IN);
|
||||
if (!IS_ERR_OR_NULL(spi_priv->reset_gpio)) {
|
||||
if (!gpiod_get_value_cansleep(spi_priv->reset_gpio)) {
|
||||
dev_err(dev, "the remote dsp should be powered!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_init(&spi_priv->lock);
|
||||
|
||||
spi->bits_per_word = 8;
|
||||
ret = spi_setup(spi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "ERR:fail to setup spi\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spi_priv->spi = spi;
|
||||
spi_priv->dev = dev;
|
||||
dev_set_drvdata(dev, spi_priv);
|
||||
|
||||
ret = devm_snd_soc_register_component(dev, &spi_codec_component,
|
||||
&spi_codec_dai, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spi_codec_remove(struct spi_device *spi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id spi_codec_device_id[] = {
|
||||
{ .compatible = "rockchip,spi-codec", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, spi_codec_device_id);
|
||||
|
||||
static struct spi_driver spi_codec_driver = {
|
||||
.driver = {
|
||||
.name = "spi_codec",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(spi_codec_device_id),
|
||||
},
|
||||
.probe = spi_codec_probe,
|
||||
.remove = spi_codec_remove,
|
||||
};
|
||||
module_spi_driver(spi_codec_driver);
|
||||
|
||||
MODULE_AUTHOR("Jun Zeng <jun.zeng@rock-chips.com>");
|
||||
MODULE_DESCRIPTION("Rockchip SPI Codec Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
49
sound/soc/codecs/rockchip-spi-codec.h
Normal file
49
sound/soc/codecs/rockchip-spi-codec.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* spi-codec.h - Rockchip Spi-Codec Driver
|
||||
*
|
||||
* Copyright (C) 2023 Rockchip Electronics Co., Ltd.
|
||||
*/
|
||||
|
||||
#ifndef __SPI_CODEC_H__
|
||||
#define __SPI_CODEC_H__
|
||||
|
||||
#define PAYLOAD_MAX (4096)
|
||||
|
||||
#define SS_CMD_BEGIN (0xF4190BE6UL)
|
||||
|
||||
#define SS_CMD_PROGRAM (0xFAAD0552UL)
|
||||
#define SS_CMD_PARAMETER_SAFE (0xA5015AFEUL)
|
||||
#define SS_CMD_BLOCK_PARAMETER_SAFE (0x4EA5B15AUL)
|
||||
#define SS_CMD_BLOCK_PARAMETER_NO_SAFE (0xFFA1F05EUL)
|
||||
|
||||
#define SS_CMD_END (0xF1D20E2DUL)
|
||||
#define SS_CMD_PARTIAL_END (0xE1D21E2DUL)
|
||||
|
||||
#define SS_CMD_READ_REQUEST (0xF3D20C2DUL)
|
||||
#define SS_CMD_BK_MIPS_VALUE (0x00CE6319UL)
|
||||
#define SS_CMD_BK_ERROR_CODE (0x00F7E808UL)
|
||||
#define SS_CMD_BK_READ_VALUE (0x00BC543AUL)
|
||||
#define SS_CMD_BK_ACKNOWLEDGE (0x00000000UL)
|
||||
#define SS_CMD_BK_VERSION_INFO (0x003EDC12UL)
|
||||
|
||||
struct spi_codec_protocol_packet {
|
||||
unsigned int cmd_begin;
|
||||
|
||||
/* indicates the nature of payload */
|
||||
unsigned int cmd_type;
|
||||
|
||||
/* payload data length */
|
||||
unsigned int payload_len;
|
||||
unsigned int payload[PAYLOAD_MAX];
|
||||
|
||||
/* payload or entire packet */
|
||||
unsigned int crc;
|
||||
|
||||
/* parameter id */
|
||||
unsigned int address;
|
||||
|
||||
unsigned int cmd_end;
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user