blob: 65ffec8b3a39d74e38ecf580f5c07a303f3fb160 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
//
// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver.
//
// Copyright (C) 2026 Nuvoton Technology Corp.
// Author: David Lin <ctlin0@nuvoton.com>
// Seven Lee <wtli@nuvoton.com>
// John Hsu <kchsu0@nuvoton.com>
// Neo Chang <ylchang2@nuvoton.com>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/bitfield.h>
#include <sound/soc.h>
#include "nau8360-dsp.h"
#include "nau8360.h"
#define NAU8360_DSP_IDLE_RETRY 10
static const unsigned short nau8360_dsp_addr[NAU8360_DSP_FW_NUM] = {
NAU8360_RF000_DSP_COMM, NAU8360_RF002_DSP_COMM };
static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp,
const char *fw_name, int dsp_addr);
#define NAU_DSP_CMD(_id, _msg, _setup, _reply) \
[_id] = { \
.cmd_id = _id, \
.msg_param = _msg, \
.setup_data = _setup, \
.reply_data = _reply, \
}
#define NAU_DSP_CMD_ID(_id) \
[_id] = { \
.cmd_id = _id, \
}
/*
* Preamble Fragment Masks:
* FIELD | BITS | SHIFT | MASK | DETAILS
* ----------------|--------|-------|------|---------------------------------
* length high | [31:24]| 24 | 0xff | Upper 8 bits of total length (LEN[9:2])
* cmd_id | [23:18]| 18 | 0x3f | Command identifier (6 bits)
* length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length (LEN[1:0])
* preamble magic | [15:0] | 0 |0xffff| Preamble signature (0xB2A1)
*/
#define NAU8360_HOST_LEN_HIGH_MASK GENMASK(31, 24)
#define NAU8360_HOST_CMD_ID_MASK GENMASK(23, 18)
#define NAU8360_HOST_LEN_LOW_MASK GENMASK(17, 16)
#define NAU8360_HOST_PREAMBLE_MASK GENMASK(15, 0)
/*
* Payload Fragment Masks:
* FIELD | BITS | SHIFT | MASK | DETAILS
* ----------------|--------|-------|------|---------------------------------
* param_size | [31:16]| 16 |0xffff| Size of parameter data (16 bits)
* param_offset | [15:0] | 0 |0xffff| Starting offset (16 bits)
*/
#define NAU8360_HOST_PARAM_SIZE_MASK GENMASK(31, 16)
#define NAU8360_HOST_PARAM_OFFSET_MASK GENMASK(15, 0)
/*
* Trailing Fragment Masks:
* FIELD | BITS | SHIFT | MASK | DETAILS
* ----------------|--------|-------|------|---------------------------------
* length high | [15:14]| 14 | 0x03 | Upper 2 bits of total length
* padding | [13:12]| 12 | 0x03 | Padding bytes count
* length low | [7:0] | 0 | 0xff | Lower 8 bits of total length
* Note: Reassembled length = (length_high << 8) | length_low (Total 10 bits)
*/
#define NAU8360_TRAIL_LEN_LOW_MASK GENMASK(7, 0)
#define NAU8360_TRAIL_PAD_MASK GENMASK(13, 12)
#define NAU8360_TRAIL_LEN_HIGH_MASK GENMASK(15, 14)
/*
* Reply Preamble Masks:
* FIELD | BITS | SHIFT | MASK | DETAILS
* ----------------|--------|-------|------|---------------------------------
* length high | [31:24]| 24 | 0xff | Upper 8 bits of total length
* reply_id | [23:18]| 18 | 0x3f | Reply identifier (6 bits)
* length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length
* Note: Reassembled length = (length_high << 2) | length_low (Total 10 bits)
*/
#define NAU8360_REPLY_LEN_HIGH_MASK GENMASK(31, 24)
#define NAU8360_REPLY_ID_MASK GENMASK(23, 18)
#define NAU8360_REPLY_LEN_LOW_MASK GENMASK(17, 16)
static const struct nau8360_cmd_info nau8360_dsp_cmd_table[] = {
/* { cmd_id, msg_param, setup_data, reply_data} */
NAU_DSP_CMD(NAU8360_DSP_CMD_GET_COUNTER, 0, 0, 1),
NAU_DSP_CMD(NAU8360_DSP_CMD_GET_FRAME_STATUS, 0, 0, 1),
NAU_DSP_CMD(NAU8360_DSP_CMD_GET_REVISION, 0, 0, 1),
NAU_DSP_CMD(NAU8360_DSP_CMD_GET_KCS_RSLTS, 1, 0, 1),
NAU_DSP_CMD(NAU8360_DSP_CMD_GET_KCS_SETUP, 1, 0, 1),
NAU_DSP_CMD(NAU8360_DSP_CMD_SET_KCS_SETUP, 1, 1, 0),
NAU_DSP_CMD_ID(NAU8360_DSP_CMD_CLK_STOP),
NAU_DSP_CMD_ID(NAU8360_DSP_CMD_CLK_RESTART),
};
static const char *const dsp_cmd_table[] = {
[NAU8360_DSP_CMD_GET_COUNTER] = "GET_COUNTER",
[NAU8360_DSP_CMD_GET_FRAME_STATUS] = "GET_FRAME_STATUS",
[NAU8360_DSP_CMD_GET_REVISION] = "GET_REVISION",
[NAU8360_DSP_CMD_GET_KCS_RSLTS] = "GET_KCS_RSLTS",
[NAU8360_DSP_CMD_GET_KCS_SETUP] = "GET_KCS_SETUP",
[NAU8360_DSP_CMD_SET_KCS_SETUP] = "SET_KCS_SETUP",
[NAU8360_DSP_CMD_CLK_STOP] = "CLK_STOP",
[NAU8360_DSP_CMD_CLK_RESTART] = "CLK_RESTART",
};
static int nau8360_dsp_idle(struct snd_soc_component *cp, unsigned short dsp_addr)
{
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
unsigned int idle_pattern, timeout = NAU8360_DSP_IDLE_RETRY * USEC_PER_MSEC;
int ret = 0;
ret = regmap_read_poll_timeout(nau8360->regmap, dsp_addr, idle_pattern,
idle_pattern == NAU8360_DSP_COMM_IDLE_WORD, USEC_PER_MSEC, timeout);
if (ret)
dev_err(nau8360->dev, "Timeout waiting for DSP idle state: %d", ret);
return ret;
}
/**
* nau8360_pack_preamble - Pack DSP preamble fragment
* @cmd_id: Command ID for the DSP message
* @frag_len: Total length of the message fragments
*
* Return: 32-bit packed payload in Little Endian format.
*/
static inline u32 nau8360_pack_preamble(u8 cmd_id, u16 frag_len)
{
return FIELD_PREP(NAU8360_HOST_PREAMBLE_MASK, NAU8360_DSP_COMM_PREAMBLE) |
FIELD_PREP(NAU8360_HOST_CMD_ID_MASK, cmd_id) |
FIELD_PREP(NAU8360_HOST_LEN_LOW_MASK, frag_len) |
FIELD_PREP(NAU8360_HOST_LEN_HIGH_MASK, frag_len >> 2);
}
/**
* nau8360_pack_param - Pack DSP parameter fragment
* @param_offset: Starting offset of the parameter data
* @param_size: Size of the parameter data in bytes
*
* Return: 32-bit packed payload in Little Endian format.
*/
static inline u32 nau8360_pack_param(u16 param_offset, u16 param_size)
{
return FIELD_PREP(NAU8360_HOST_PARAM_OFFSET_MASK, param_offset) |
FIELD_PREP(NAU8360_HOST_PARAM_SIZE_MASK, param_size);
}
/**
* nau8360_pack_trailing - Pack DSP trailing fragment
* @frag_cnt: Current fragment count
* @padding: Number of padding bytes added to the final data fragment
*
* Return: 32-bit packed payload in Little Endian format.
*/
static inline u32 nau8360_pack_trailing(u16 frag_cnt, u8 padding)
{
return FIELD_PREP(NAU8360_TRAIL_LEN_LOW_MASK, frag_cnt) |
FIELD_PREP(NAU8360_TRAIL_PAD_MASK, padding) |
FIELD_PREP(NAU8360_TRAIL_LEN_HIGH_MASK, frag_cnt >> 8);
}
static void nau8360_send_data_payload(struct snd_soc_component *cp,
unsigned short dsp_addr, const void *param_data, int param_size,
int *frag_cnt, int *padding)
{
const u8 *data = (const u8 *)param_data;
u32 payload = 0;
int i, data_size = 0;
for (i = 0; i < param_size; i++) {
payload |= data[i] << (data_size * 8);
data_size++;
if (data_size == NAU8360_DSP_DATA_BYTE) {
snd_soc_component_write(cp, dsp_addr, payload);
data_size = 0;
payload = 0;
(*frag_cnt)++;
}
}
if (data_size > 0) {
*padding = NAU8360_DSP_DATA_BYTE - data_size;
snd_soc_component_write(cp, dsp_addr, payload);
(*frag_cnt)++;
}
}
static int nau8360_message_to_dsp(struct snd_soc_component *cp,
const struct nau8360_cmd_info *cmd_info, int frag_len, int param_offset,
int param_size, void *param_data, unsigned short dsp_addr)
{
unsigned int payload;
int ret, padding = 0, frag_cnt = 0;
ret = nau8360_dsp_idle(cp, dsp_addr);
if (ret)
return ret;
/* sending preamble fragment */
payload = nau8360_pack_preamble(cmd_info->cmd_id, frag_len);
snd_soc_component_write(cp, dsp_addr, payload);
if (!cmd_info->msg_param)
return 0;
/* sending payload + padding */
payload = nau8360_pack_param(param_offset, param_size);
snd_soc_component_write(cp, dsp_addr, payload);
frag_cnt++;
if (cmd_info->setup_data)
nau8360_send_data_payload(cp, dsp_addr, param_data, param_size,
&frag_cnt, &padding);
/* sending trailing fragment */
frag_cnt++;
payload = nau8360_pack_trailing(frag_cnt, padding);
snd_soc_component_write(cp, dsp_addr, payload);
if (frag_cnt != frag_len) {
dev_err(cp->dev, "message error (CMD_ID 0x%x, LEN 0x%x) !!!",
cmd_info->cmd_id, frag_cnt);
return -EPROTO;
}
return 0;
}
static int nau8360_dsp_replied(struct nau8360 *nau8360, int *length,
unsigned short dsp_addr)
{
unsigned int reply_preamble;
int ret, reply_id;
ret = regmap_read_poll_timeout(nau8360->regmap, dsp_addr, reply_preamble,
(reply_preamble & 0xffff) == NAU8360_DSP_COMM_PREAMBLE,
USEC_PER_MSEC, NAU8360_DSP_IDLE_RETRY * USEC_PER_MSEC);
if (ret) {
dev_err(nau8360->dev, "timeout for reply preamble: %d", ret);
return ret;
}
*length = FIELD_GET(NAU8360_REPLY_LEN_LOW_MASK, reply_preamble);
*length |= FIELD_GET(NAU8360_REPLY_LEN_HIGH_MASK, reply_preamble) << 2;
reply_id = FIELD_GET(NAU8360_REPLY_ID_MASK, reply_preamble);
return (reply_id != NAU8360_DSP_REPLY_OK) ? -reply_id : 0;
}
static int nau8360_validate_trailing(struct snd_soc_component *cp,
unsigned short dsp_addr, int frag_len, int pad_len_exp)
{
int ret, len_pos, pad_len;
unsigned int payload;
struct device *dev = cp->dev;
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
/* reading trailing fragment */
ret = regmap_read(nau8360->regmap, dsp_addr, &payload);
if (ret) {
dev_err(dev, "failed to read trailing fragment");
return ret;
}
len_pos = FIELD_GET(NAU8360_TRAIL_LEN_LOW_MASK, payload);
len_pos |= FIELD_GET(NAU8360_TRAIL_LEN_HIGH_MASK, payload) << 8;
if (len_pos != frag_len) {
dev_err(dev, "LEN_POST %02X, expect %02X", len_pos, frag_len);
return -EPROTO;
}
pad_len = FIELD_GET(NAU8360_TRAIL_PAD_MASK, payload);
if (pad_len != pad_len_exp) {
dev_err(dev, "PAD_LEN %02X, expect %02X", pad_len, pad_len_exp);
return -EPROTO;
}
return 0;
}
static int nau8360_read_data_payload(struct snd_soc_component *cp,
unsigned short dsp_addr, int frag_len, bool msg_param,
void *data, int data_size, int *data_count)
{
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
struct device *dev = cp->dev;
unsigned int payload;
u32 *data_buf = data;
int i, j, ret;
*data_count = (msg_param) ? data_size : 0;
for (i = 0; i < frag_len - 1; i++) {
ret = regmap_read(nau8360->regmap, dsp_addr, &payload);
if (ret) {
dev_err(dev, "failed to read payload of dsp");
return ret;
}
if (!msg_param) {
*data_buf++ = payload;
break;
}
if (*data_count >= NAU8360_DSP_DATA_BYTE) {
*data_buf++ = payload;
*data_count -= NAU8360_DSP_DATA_BYTE;
} else if (*data_count > 0) {
for (j = 0; j < *data_count; j++)
((u8 *)data_buf)[j] = (payload >> (j * 8)) & 0xff;
*data_count = 0;
}
if (*data_count == 0)
break;
}
return 0;
}
static int nau8360_reply_from_dsp(struct snd_soc_component *cp,
const struct nau8360_cmd_info *cmd_info, int data_size,
void *data, unsigned short dsp_addr)
{
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
struct device *dev = cp->dev;
int ret, frag_len, pad_len_exp, data_count = 0;
ret = nau8360_dsp_replied(nau8360, &frag_len, dsp_addr);
if (ret)
return ret;
if (!frag_len || !cmd_info->reply_data)
return 0;
if (!data || frag_len == 1)
return -EINVAL;
ret = nau8360_read_data_payload(cp, dsp_addr, frag_len,
cmd_info->msg_param, data, data_size, &data_count);
if (ret)
return ret;
/* check the reply length same as request */
if (data_count && (cmd_info->cmd_id == NAU8360_DSP_CMD_GET_KCS_RSLTS ||
cmd_info->cmd_id == NAU8360_DSP_CMD_GET_KCS_SETUP))
dev_warn(dev, "payload_len %d, expected %d",
data_size - data_count, data_size);
pad_len_exp = cmd_info->msg_param ?
(frag_len - 1) * NAU8360_DSP_DATA_BYTE - (data_size - data_count) : 0;
return nau8360_validate_trailing(cp, dsp_addr, frag_len, pad_len_exp);
}
/**
* nau8360_send_dsp_command - Send command to DSP
*
* @cp: component to register
* @cmd_id: DSP supported command ID
* @kcs_setup: KCS setup structure
* @dsp_addr: DSP address
*
* The communication protocol is a Master-Slave type protocol
* where the host processor is the master and DSP is the slave.
* The Master initiates the communication and can either write or
* read back from the slave.
* Transactions from the Master are called "Messages",
* and read-back data from the Slave is called a "Reply".
*
* The function sends command to DSP according to the command ID.
* These commands include getting the information of DSP,
* getting or setting KCS configuration, or making DSP control.
*/
static int nau8360_send_dsp_command(struct snd_soc_component *cp, int cmd_id,
struct nau8360_kcs_setup *kcs_setup, unsigned short dsp_addr)
{
const struct nau8360_cmd_info *cmd_info;
int ret = 0, frag_len = 0;
cmd_info = &nau8360_dsp_cmd_table[cmd_id];
if ((cmd_info->msg_param && !kcs_setup->set_len) ||
(cmd_info->setup_data && !kcs_setup->set_kcs_data) ||
(cmd_info->reply_data && !kcs_setup->get_data))
return -EFAULT;
/* Read up to 1kB data because the LEN field to request data is 10-bits
* long; and not beyond 3kB offset.
*/
if (cmd_id == NAU8360_DSP_CMD_GET_KCS_SETUP &&
(kcs_setup->set_len > NAU8360_DSP_KCS_DAT_LEN_MAX ||
kcs_setup->set_kcs_offset > NAU8360_DSP_KCS_OFFSET_MAX))
return -ERANGE;
/* one fragment for offset and size parameters
* one fragment for a postamble fragment
*/
if (cmd_info->msg_param)
frag_len += 2;
/* fragments for KCS setup writen */
if (cmd_info->setup_data)
frag_len += DIV_ROUND_UP(kcs_setup->set_len, NAU8360_DSP_DATA_BYTE);
ret = nau8360_message_to_dsp(cp, cmd_info, frag_len,
kcs_setup->set_kcs_offset, kcs_setup->set_len,
kcs_setup->set_kcs_data, dsp_addr);
if (ret)
return ret;
return nau8360_reply_from_dsp(cp, cmd_info, kcs_setup->get_len,
kcs_setup->get_data, dsp_addr);
}
static inline int nau8360_dsp_exec_command(struct snd_soc_component *cp, int cmd_id,
int offset, int set_len, void *set_data, int get_len, void *get_data,
int dsp_addr)
{
struct nau8360_kcs_setup kcs_setup = {
.set_kcs_offset = offset,
.set_len = set_len,
.set_kcs_data = set_data,
.get_len = get_len,
.get_data = get_data,
};
return nau8360_send_dsp_command(cp, cmd_id, &kcs_setup, dsp_addr);
}
static inline int nau8360_send_dsp_broadcast(struct snd_soc_component *cp, int cmd_id)
{
int i, ret;
for (i = 0; i < NAU8360_DSP_FW_NUM; i++) {
ret = nau8360_dsp_exec_command(cp, cmd_id, 0, 0, NULL, 0, NULL,
nau8360_dsp_addr[i]);
if (ret) {
dev_err(cp->dev, "DSP %x fail (%d)", nau8360_dsp_addr[i], ret);
return ret;
}
}
return 0;
}
/**
* nau8360_dsp_kcs_setup - Send KCS setup command to DSP
*
* @cp: component to register
* @offset: address offset relative to KCS start
* @size: size of data writen to KCS
* @data: data writen to KCS setup
* @dsp_addr : DSP address
*
* The function sends KCS setup command to DSP for
* setting KCS configuration. The maximum size that you can transfer into
* the DSP is 96 bytes. Therefore, the driver has to split the data into
* 96 bytes chucks, if the setup configuration over the threshold.
*/
static int nau8360_dsp_kcs_setup(struct snd_soc_component *cp, int offset, int size,
const void *data, unsigned short dsp_addr)
{
u8 *data_buf = (u8 *)data;
unsigned int kcs_rst = 0;
int retries = 0, ret, data_len, data_rem, addr_offset;
/* Limit full load of KCS_SETUP data and not beyond 3kB offset. */
if (!data || size > NAU8360_DSP_KCS_DAT_LEN_MAX ||
offset > NAU8360_DSP_KCS_OFFSET_MAX)
return -EINVAL;
/* sending fragments for KCS setup */
addr_offset = offset;
data_rem = size;
while (data_rem) {
data_len = min(data_rem, NAU8360_DSP_KCS_TX_MAX);
ret = nau8360_dsp_exec_command(cp, NAU8360_DSP_CMD_SET_KCS_SETUP,
addr_offset, data_len, (void *)data_buf, 0, &kcs_rst, dsp_addr);
if (ret) {
if (retries++ < NAU8360_DSP_RETRY_MAX)
continue;
return ret;
}
data_buf += (u8)data_len;
addr_offset += data_len;
data_rem -= data_len;
/* checking KCS result */
ret = nau8360_dsp_exec_command(cp, NAU8360_DSP_CMD_GET_KCS_RSLTS,
0, NAU8360_DSP_DATA_BYTE, NULL,
NAU8360_DSP_DATA_BYTE, &kcs_rst, dsp_addr);
if (ret)
return ret;
if (kcs_rst != NAU8360_DSP_KCS_RSLTS_SUCCESS)
return -EINVAL;
}
return 0;
}
static int nau8360_dsp_get_cmd_put(struct snd_soc_component *cp,
int dsp_addr, int cmd, int *value)
{
struct device *dev = cp->dev;
int ret;
dev_dbg(dev, "send DSP %x command %s", dsp_addr, dsp_cmd_table[cmd]);
ret = nau8360_dsp_exec_command(cp, cmd, 0, sizeof(int), NULL,
sizeof(int), value, dsp_addr);
if (ret) {
dev_err(dev, "do command fail (%d)", ret);
return ret;
}
return 0;
}
static int nau8360_dsp_clock_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm);
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
int ret = 0;
mutex_lock(&nau8360->lock);
if (SND_SOC_DAPM_EVENT_ON(event))
ret = nau8360_send_dsp_broadcast(cp, NAU8360_DSP_CMD_CLK_RESTART);
else if (SND_SOC_DAPM_EVENT_OFF(event))
ret = nau8360_send_dsp_broadcast(cp, NAU8360_DSP_CMD_CLK_STOP);
mutex_unlock(&nau8360->lock);
return ret;
}
static const struct snd_soc_dapm_widget nau8360_dsp_dapm_widgets[] = {
SND_SOC_DAPM_SUPPLY("DSP Clock", SND_SOC_NOPM, 0, 0, nau8360_dsp_clock_event,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
};
static const struct snd_soc_dapm_route nau8360_dsp_dapm_routes[] = {
{ "DSP", NULL, "HW3 Engine" },
{ "DSP", NULL, "DSP Clock" },
};
static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp,
const char *fw_name, int dsp_addr)
{
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
const struct firmware *fw;
int buf_off, buf_len;
int ret = 0, status = 0;
mutex_lock(&nau8360->lock);
ret = nau8360_dsp_get_cmd_put(cp, dsp_addr,
NAU8360_DSP_CMD_GET_FRAME_STATUS, &status);
mutex_unlock(&nau8360->lock);
if (ret || !(status & NAU8360_DSP_ALGO_OK)) {
dev_err(cp->dev, "DSP %x is not ready", dsp_addr);
return -EIO;
}
dev_info(cp->dev, "DSP %x is ready to load firmware %s, status %x",
dsp_addr, fw_name, status);
ret = request_firmware(&fw, fw_name, cp->dev);
if (ret) {
dev_err(cp->dev, "failed to load firmware (%d)", ret);
return ret;
}
buf_off = 0;
buf_len = nau8360->kcs_setup_size = fw->size;
mutex_lock(&nau8360->lock);
ret = nau8360_dsp_kcs_setup(cp, buf_off, buf_len, fw->data, dsp_addr);
mutex_unlock(&nau8360->lock);
if (ret) {
dev_err(cp->dev, "send DSP command %s fail (%d)",
dsp_cmd_table[NAU8360_DSP_CMD_SET_KCS_SETUP], ret);
}
release_firmware(fw);
return ret;
}
int nau8360_dsp_init(struct snd_soc_component *cp)
{
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
int i, ret;
for (i = 0; i < NAU8360_DSP_FW_NUM; i++) {
ret = nau8360_dsp_chan_kcs_setup(cp, nau8360->dsp_firmware[i], nau8360_dsp_addr[i]);
if (ret)
return ret;
}
return 0;
}
int nau8360_dsp_setup_controls(struct snd_soc_component *cp)
{
struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp);
struct snd_soc_dapm_context *dapm = nau8360->dapm;
int ret;
ret = snd_soc_dapm_new_controls(dapm, nau8360_dsp_dapm_widgets,
ARRAY_SIZE(nau8360_dsp_dapm_widgets));
if (ret) {
dev_err(cp->dev, "add DSP widget fail (%d)", ret);
return ret;
}
ret = snd_soc_dapm_add_routes(dapm, nau8360_dsp_dapm_routes,
ARRAY_SIZE(nau8360_dsp_dapm_routes));
if (ret) {
dev_err(cp->dev, "add DSP route fail (%d)", ret);
return ret;
}
return 0;
}