375 lines
10 KiB
C
375 lines
10 KiB
C
// 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);
|
|
unsigned int ch = (unsigned int)kcontrol->private_value;
|
|
|
|
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);
|
|
unsigned int ch = (unsigned int)kcontrol->private_value;
|
|
|
|
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 void spi_codec_remove(struct spi_device *spi)
|
|
{
|
|
}
|
|
|
|
static const struct of_device_id spi_codec_device_id[] = {
|
|
{ .compatible = "rockchip,spi-codec", },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, spi_codec_device_id);
|
|
|
|
static const struct spi_device_id spi_codec_id[] = {
|
|
{ "spi-codec" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(spi, spi_codec_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),
|
|
},
|
|
.id_table = spi_codec_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");
|