From bf10d7edd82707add9e4fdc5328c50ee6c196820 Mon Sep 17 00:00:00 2001 From: Jianqun Xu Date: Thu, 9 Nov 2023 17:38:03 +0800 Subject: [PATCH] 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 Signed-off-by: Jun Zeng --- sound/soc/codecs/Kconfig | 10 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rockchip-spi-codec.c | 370 ++++++++++++++++++++++++++ sound/soc/codecs/rockchip-spi-codec.h | 49 ++++ 4 files changed, 431 insertions(+) create mode 100644 sound/soc/codecs/rockchip-spi-codec.c create mode 100644 sound/soc/codecs/rockchip-spi-codec.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 827c8b33064b..836c9e1545db 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -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 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fd3e6861e75c..f27f7ff5d8fb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -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 diff --git a/sound/soc/codecs/rockchip-spi-codec.c b/sound/soc/codecs/rockchip-spi-codec.c new file mode 100644 index 000000000000..6011e9451ff3 --- /dev/null +++ b/sound/soc/codecs/rockchip-spi-codec.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Rockchip SPI Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rockchip-spi-codec.h b/sound/soc/codecs/rockchip-spi-codec.h new file mode 100644 index 000000000000..a0b13e96f570 --- /dev/null +++ b/sound/soc/codecs/rockchip-spi-codec.h @@ -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