* Phytium CODEC ALSA SoC Audio driver
*
* Copyright (C) 2023-2024, Phytium Technology Co., Ltd.
*
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <linux/clocksource.h>
#include <linux/random.h>
#include <linux/timecounter.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <linux/pci.h>
#include <linux/version.h>
#include <linux/acpi.h>
#include <sound/tlv.h>
#include <linux/i2c.h>
#include "phytium-codec-v2.h"
#define PHYT_CODEC_V2_VERSION "1.1.1"
#define PHYTIUM_RATES (SNDRV_PCM_RATE_192000 | \
SNDRV_PCM_RATE_96000 | \
SNDRV_PCM_RATE_88200 | \
SNDRV_PCM_RATE_8000_48000)
#define PHYTIUM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S18_3LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_kcontrol_new phyt_snd_controls[] = {
SOC_SINGLE("PCM Volume", PHYTIUM_CODEC_PLAYBACKE_VOL, 0, 0xc0, 0),
SOC_SINGLE("Playback Volume1", PHYTIUM_CODEC_PLAYBACKE_OUT1_VOL, 0, 0x24, 0),
SOC_SINGLE("Playback Volume2", PHYTIUM_CODEC_PLAYBACKE_OUT2_VOL, 0, 0x24, 0),
SOC_SINGLE("Capture Digital Volume", PHYTIUM_CODEC_CAPTURE_VOL, 0, 0xc0, 0),
SOC_SINGLE("Mic PGA Volume", PHYTIUM_CODEC_CAPTURE_IN1_VOL, 0, 8, 0),
};
* DAPM Controls
*/
static const char * const phyt_mux_sel[] = {
"Line 1", "Line 2"};
static const struct soc_enum phyt_mux_enum =
SOC_ENUM_SINGLE(PHYTIUM_CODEC_INMUX_SEL, 0,
ARRAY_SIZE(phyt_mux_sel),
phyt_mux_sel);
static const struct snd_kcontrol_new phyt_pga_controls =
SOC_DAPM_ENUM("Route", phyt_mux_enum);
static const struct snd_soc_dapm_widget phyt_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("INPUT1"),
SND_SOC_DAPM_INPUT("INPUT2"),
SND_SOC_DAPM_MUX("Input Mux", PHYTIUM_CODEC_INMUX_ENABLE, 0, 0, &phyt_pga_controls),
SND_SOC_DAPM_ADC("Input ADC", "Capture", PHYTIUM_CODEC_ADC_ENABLE, 0, 0),
SND_SOC_DAPM_DAC("Output DAC", "Playback", PHYTIUM_CODEC_DAC_ENABLE, 0, 0),
SND_SOC_DAPM_OUTPUT("OUTPUT1"),
SND_SOC_DAPM_OUTPUT("OUTPUT2"),
};
static const struct snd_soc_dapm_route phyt_dapm_routes[] = {
{ "Input Mux", "Line 1", "INPUT1"},
{ "Input Mux", "Line 2", "INPUT2"},
{ "Input ADC", NULL, "Input Mux"},
{ "OUTPUT1", NULL, "Output DAC"},
{ "OUTPUT2", NULL, "Output DAC"},
};
static void phyt_codec_show_status(uint8_t status)
{
switch (status) {
case ERR_CODEC_SUCCESS:
pr_err("success\n");
break;
case ERR_CODEC_DEV_BUSY:
pr_err("device busy\n");
break;
case ERR_CODEC_RW_ERROR:
pr_err("read/write error\n");
break;
case ERR_CODEC_NODEV:
pr_err("no hw device\n");
break;
case ERR_CODEC_NO_INIT:
pr_err("no init\n");
break;
default:
pr_err("unknown error: %d\n", status);
break;
}
}
int phyt_codec_msg_set_cmd(struct phytium_codec *priv)
{
struct phytcodec_cmd *ans_msg;
int timeout = 5000, ret = 0;
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_AP2RV_INT_STATE, SEND_INTR);
ans_msg = priv->sharemem_base;
while (timeout && (ans_msg->complete == PHYTCODEC_COMPLETE_NOT_READY
|| ans_msg->complete == PHYTCODEC_COMPLETE_GOING)) {
udelay(200);
timeout--;
}
if (timeout == 0) {
dev_err(priv->dev, "failed to receive msg, timeout\n");
return -EINVAL;
} else if (ans_msg->complete == PHYTCODEC_COMPLETE_SUCCESS) {
dev_dbg(priv->dev, "receive msg successfully\n");
if (ans_msg->status != 0) {
phyt_codec_show_status(ans_msg->status);
dev_err(priv->dev, "controller status error code:%d\n",
ans_msg->status);
return -EINVAL;
}
} else if (ans_msg->complete != PHYTCODEC_COMPLETE_SUCCESS) {
phyt_codec_show_status(ans_msg->status);
dev_err(priv->dev, "receive msg; error code:%d\n",
ans_msg->complete);
ret = -EINVAL;
} else {
dev_err(priv->dev, "unknown error");
ret = -EINVAL;
}
return ret;
}
static int phyt_set_cmd(struct phytium_codec *priv,
unsigned int cmd)
{
struct phytcodec_cmd *msg = priv->sharemem_base;
int ret = 0;
msg->reserved = 0;
msg->seq = 0;
msg->cmd_id = PHYTCODEC_MSG_CMD_SET;
msg->cmd_subid = cmd;
msg->complete = 0;
ret = phyt_codec_msg_set_cmd(priv);
if (ret < 0) {
dev_err(priv->dev, "set cmd_subid 0x%x failed\n", cmd);
ret = -EINVAL;
goto error;
}
error:
return ret;
}
static int phyt_pm_cmd(struct phytium_codec *priv,
unsigned int cmd)
{
struct phytcodec_cmd *msg = priv->sharemem_base;
uint16_t total_regs_len;
uint8_t *regs;
int ret = 0, i = 0, cnt = 1;
memset(msg, 0, sizeof(struct phytcodec_cmd));
msg->reserved = 0;
msg->seq = 0;
msg->cmd_id = PHYTCODEC_MSG_CMD_SET;
msg->cmd_subid = cmd;
msg->complete = 0;
msg->cmd_para.phytcodec_reg.cnt = 0;
if (cmd == PHYTCODEC_MSG_CMD_SET_RESUME) {
memcpy(msg->cmd_para.phytcodec_reg.regs, priv->regs, REG_SH_LEN);
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_INT_MASK, 0x0);
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_INT_ENABLE, 0x1);
}
ret = phyt_codec_msg_set_cmd(priv);
if (ret < 0) {
dev_err(priv->dev, "set cmd_subid 0x%x failed\n", cmd);
return -EINVAL;
}
total_regs_len = msg->cmd_para.phytcodec_reg.total_regs_len;
if (total_regs_len % REG_SH_LEN == 0)
cnt = total_regs_len / REG_SH_LEN;
else
cnt = total_regs_len / REG_SH_LEN + 1;
if (cmd == PHYTCODEC_MSG_CMD_SET_SUSPEND) {
regs = kmalloc(total_regs_len, GFP_KERNEL);
priv->regs = regs;
for (i = 1; i < cnt; i++) {
if (msg->cmd_para.phytcodec_reg.cnt != i) {
dev_err(priv->dev, "error phytcodec_reg.cnt\n");
ret = -EINVAL;
goto error;
}
memcpy(regs, msg->cmd_para.phytcodec_reg.regs, REG_SH_LEN);
regs += REG_SH_LEN;
msg->complete = 0;
ret = phyt_codec_msg_set_cmd(priv);
if (ret < 0) {
dev_err(priv->dev, "set cmd_subid 0x%x failed\n", cmd);
ret = -EINVAL;
goto error;
}
}
memcpy(regs, msg->cmd_para.phytcodec_reg.regs,
total_regs_len - REG_SH_LEN * (msg->cmd_para.phytcodec_reg.cnt - 1));
} else if (cmd == PHYTCODEC_MSG_CMD_SET_RESUME) {
regs = priv->regs;
for (i = 1; i < cnt; i++) {
if (msg->cmd_para.phytcodec_reg.cnt != i) {
dev_err(priv->dev, "error phytcodec_reg.cnt\n");
ret = -EINVAL;
goto error;
}
regs += REG_SH_LEN;
memcpy(msg->cmd_para.phytcodec_reg.regs, regs, REG_SH_LEN);
msg->complete = 0;
ret = phyt_codec_msg_set_cmd(priv);
if (ret < 0) {
dev_err(priv->dev, "set cmd_subid 0x%x failed\n", cmd);
ret = -EINVAL;
goto error;
}
}
kfree(priv->regs);
priv->regs = NULL;
}
return ret;
error:
kfree(priv->regs);
priv->regs = NULL;
return ret;
}
static int phyt_get_cmd(struct phytium_codec *priv, unsigned int cmd)
{
struct phytcodec_cmd *msg = priv->sharemem_base;
int ret = 0, i = 0, cnt = 1;
uint16_t total_regs_len;
uint8_t *regs;
msg->reserved = 0;
msg->seq = 0;
msg->cmd_id = PHYTCODEC_MSG_CMD_GET;
msg->cmd_subid = cmd;
msg->complete = 0;
if (cmd == PHYTCODEC_MSG_CMD_GET_ALL_REGS)
msg->cmd_para.phytcodec_reg.cnt = 0;
ret = phyt_codec_msg_set_cmd(priv);
if (ret < 0) {
dev_err(priv->dev, "get cmd_subid 0x%x failed\n", cmd);
return -EINVAL;
}
total_regs_len = msg->cmd_para.phytcodec_reg.total_regs_len;
if (cmd == PHYTCODEC_MSG_CMD_GET_ALL_REGS) {
if (total_regs_len % REG_SH_LEN == 0)
cnt = total_regs_len / REG_SH_LEN;
else
cnt = total_regs_len / REG_SH_LEN + 1;
regs = kmalloc(total_regs_len, GFP_KERNEL);
priv->regs = regs;
for (i = 1; i < cnt; i++) {
if (msg->cmd_para.phytcodec_reg.cnt != i) {
dev_err(priv->dev, "error phytcodec_reg.cnt\n");
ret = -EINVAL;
goto error;
}
memcpy(regs, msg->cmd_para.phytcodec_reg.regs, REG_SH_LEN);
regs += REG_SH_LEN;
msg->complete = 0;
ret = phyt_codec_msg_set_cmd(priv);
if (ret < 0) {
dev_err(priv->dev, "set cmd_subid 0x%x failed\n", cmd);
ret = -EINVAL;
goto error;
}
}
memcpy(regs, msg->cmd_para.phytcodec_reg.regs,
total_regs_len - REG_SH_LEN * (msg->cmd_para.phytcodec_reg.cnt - 1));
for (i = 0; i < total_regs_len; i++)
dev_info(priv->dev, "0x%02x-0x%02x\n", i, priv->regs[i]);
kfree(priv->regs);
priv->regs = NULL;
}
return ret;
error:
kfree(priv->regs);
priv->regs = NULL;
return -EINVAL;
}
static int phyt_probe(struct snd_soc_component *component)
{
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
return phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_PROBE);
}
static void phyt_remove(struct snd_soc_component *component)
{
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_REMOVE);
}
static int phyt_suspend(struct snd_soc_component *component)
{
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
return phyt_pm_cmd(priv, PHYTCODEC_MSG_CMD_SET_SUSPEND);
}
static int phyt_resume(struct snd_soc_component *component)
{
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
return phyt_pm_cmd(priv, PHYTCODEC_MSG_CMD_SET_RESUME);
}
static int phyt_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
int ret;
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
msg->cmd_para.para[0] = priv->channels/2;
switch (level) {
case SND_SOC_BIAS_ON:
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_BIAS_ON);
break;
case SND_SOC_BIAS_PREPARE:
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_BIAS_PREPARE);
break;
case SND_SOC_BIAS_STANDBY:
if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_BIAS_STANDBY);
else
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_BIAS_STANDBY);
break;
case SND_SOC_BIAS_OFF:
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_BIAS_OFF);
break;
}
return ret;
}
static const struct snd_soc_component_driver phyt_component_driver = {
.probe = phyt_probe,
.remove = phyt_remove,
.suspend = phyt_suspend,
.resume = phyt_resume,
.set_bias_level = phyt_set_bias_level,
.controls = phyt_snd_controls,
.num_controls = ARRAY_SIZE(phyt_snd_controls),
.dapm_widgets = phyt_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(phyt_dapm_widgets),
.dapm_routes = phyt_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(phyt_dapm_routes),
.suspend_bias_off = 1,
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
};
static int phyt_mute(struct snd_soc_dai *dai, int mute, int direction)
{
int ret;
struct snd_soc_component *component = dai->component;
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
msg->cmd_para.para[0] = (uint8_t)direction;
if (mute)
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_MUTE);
else
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_UNMUTE);
return ret;
}
static int phyt_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret;
struct snd_soc_component *component = dai->component;
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_STARTUP);
else
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_STARTUP_RC);
return ret;
}
static void phyt_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret;
struct snd_soc_component *component = dai->component;
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_SHUTDOWN);
else
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_SHUTDOWN_RC);
}
static int phyt_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
int wl, ret = 0;
struct snd_soc_component *component = dai->component;
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
priv->channels = params_channels(params);
switch (params_width(params)) {
case PHYTCODEC_FORMAT_S16:
wl = 3;
break;
case PHYTCODEC_FORMAT_S18:
wl = 2;
break;
case PHYTCODEC_FORMAT_S20:
wl = 1;
break;
case PHYTCODEC_FORMAT_S24:
wl = 0;
break;
case PHYTCODEC_FORMAT_S32:
wl = 4;
break;
default:
ret = -EINVAL;
goto error;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
snd_soc_component_write(component, PHYTIUM_CODEC_HW_PARAM, wl);
else
snd_soc_component_write(component, PHYTIUM_CODEC_HW_PARAM_RC, wl);
error:
return ret;
}
static int phyt_set_dai_fmt(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
int ret;
struct snd_soc_component *component = codec_dai->component;
struct phytium_codec *priv = snd_soc_component_get_drvdata(component);
struct phytcodec_cmd *msg = priv->sharemem_base;
memset(msg, 0, sizeof(struct phytcodec_cmd));
if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
return -EINVAL;
if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S)
return -EINVAL;
if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF)
return -EINVAL;
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_DAI_FMT);
return ret;
}
static const struct snd_soc_dai_ops phyt_dai_ops = {
.startup = phyt_startup,
.shutdown = phyt_shutdown,
.hw_params = phyt_hw_params,
.mute_stream = phyt_mute,
.set_fmt = phyt_set_dai_fmt,
};
static struct snd_soc_dai_driver phyt_dai = {
.name = "phytium-hifi-v2",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = PHYTIUM_RATES,
.formats = PHYTIUM_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 2,
.channels_max = 2,
.rates = PHYTIUM_RATES,
.formats = PHYTIUM_FORMATS,
},
.ops = &phyt_dai_ops,
.symmetric_rate = 1,
};
static const struct regmap_config phyt_codec_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = REG_MAX,
.cache_type = REGCACHE_NONE,
};
void phyt_enable_debug(struct phytium_codec *priv)
{
u32 reg;
reg = phyt_readl_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG);
phyt_writel_reg(priv->regfile_base,
PHYTIUM_CODEC_DEBUG, reg | PHYTIUM_CODEC_DEBUG_ENABLE);
}
void phyt_disable_debug(struct phytium_codec *priv)
{
u32 reg;
reg = phyt_readl_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG);
reg &= ~PHYTIUM_CODEC_DEBUG_ENABLE;
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG, reg);
}
void phyt_enable_alive(struct phytium_codec *priv)
{
u32 reg;
reg = phyt_readl_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG);
phyt_writel_reg(priv->regfile_base,
PHYTIUM_CODEC_DEBUG, reg | PHYTIUM_CODEC_ALIVE_ENABLE);
}
void phyt_disable_alive(struct phytium_codec *priv)
{
u32 reg;
reg = phyt_readl_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG);
reg &= ~PHYTIUM_CODEC_ALIVE_ENABLE;
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG, reg);
}
void phyt_heartbeat(struct phytium_codec *priv)
{
u32 reg;
reg = phyt_readl_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG);
phyt_writel_reg(priv->regfile_base,
PHYTIUM_CODEC_DEBUG, reg | PHYTIUM_CODEC_HEARTBIT_VAL);
}
static void phyt_timer_handle(struct timer_list *t)
{
struct phytium_codec *priv = from_timer(priv, t, timer);
if (priv->alive_enabled && priv->heartbeat)
priv->heartbeat(priv);
mod_timer(&priv->timer, jiffies + msecs_to_jiffies(2000));
}
static int phyt_get_one_reg(struct phytium_codec *priv, uint8_t arg1, uint8_t arg2)
{
struct phytcodec_cmd *msg = priv->sharemem_base;
int ret = 0;
memset(msg, 0, sizeof(struct phytcodec_cmd));
msg->cmd_para.rw_data.addr = arg1;
msg->cmd_para.rw_data.reg = arg2;
ret = phyt_get_cmd(priv, PHYTCODEC_MSG_CMD_GET_ONE_REG);
dev_info(priv->dev, "val: 0x%x\n", msg->cmd_para.rw_data.val);
return ret;
}
static int phyt_set_one_reg(struct phytium_codec *priv, uint8_t arg1, uint8_t arg2, uint16_t arg3)
{
struct phytcodec_cmd *msg = priv->sharemem_base;
int ret = 0;
memset(msg, 0, sizeof(struct phytcodec_cmd));
msg->cmd_para.rw_data.addr = arg1;
msg->cmd_para.rw_data.reg = arg2;
msg->cmd_para.rw_data.val = arg3;
ret = phyt_set_cmd(priv, PHYTCODEC_MSG_CMD_SET_ONE_REG);
return ret;
}
static ssize_t debug_show(struct device *dev, struct device_attribute *da, char *buf)
{
struct phytium_codec *priv = dev_get_drvdata(dev);
ssize_t ret;
u32 reg;
dev_info(dev, "Usage: echo <command> [args...] > debug\n");
dev_info(dev, "Usage: echo help 1 > debug for more details");
reg = phyt_readl_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG);
ret = sprintf(buf, "%x\n", reg);
return ret;
}
static ssize_t debug_store(struct device *dev, struct device_attribute *da,
const char *buf, size_t size)
{
struct phytium_codec *priv = dev_get_drvdata(dev);
char *arg1_str = NULL, *arg2_str = NULL, *arg3_str = NULL;
uint8_t arg1 = 0, arg2 = 0;
uint16_t arg3 = 0;
char *cmd_buffer, *cmd;
long value;
u32 reg;
int status;
cmd_buffer = kmalloc(size + 1, GFP_KERNEL);
if (!cmd_buffer)
goto error;
strscpy(cmd_buffer, buf, size + 1);
cmd = strsep(&cmd_buffer, " ");
if (!cmd) {
dev_err(dev, "Invalid command argument\n");
goto error;
}
arg1_str = strsep(&cmd_buffer, " ");
if (arg1_str) {
status = kstrtoul(arg1_str, 0, &value);
if (status) {
dev_err(dev, "Invalid value for arg1: %s\n", arg1_str);
goto error;
}
arg1 = (uint8_t)value;
}
arg2_str = strsep(&cmd_buffer, " ");
if (arg2_str) {
status = kstrtoul(arg2_str, 0, &value);
if (status) {
dev_err(dev, "Invalid value for arg2: %s\n", arg2_str);
goto error;
}
arg2 = (uint8_t)value;
}
arg3_str = strsep(&cmd_buffer, " ");
if (arg3_str) {
status = kstrtou16(arg3_str, 0, &arg3);
if (status) {
dev_err(dev, "Invalid value for arg3: %s\n", arg3_str);
goto error;
}
}
if (strcmp(cmd, "dbg") == 0) {
if (!arg1_str || !arg2_str) {
dev_err(dev, "debug command requires two arguments\n");
goto error;
}
reg = phyt_readl_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG);
if (arg1 == 1) {
if (arg2 == 1) {
priv->alive_enabled = true;
reg |= BIT(arg1);
} else if (arg2 == 0) {
priv->alive_enabled = false;
reg &= ~BIT(arg1);
} else {
dev_err(dev, "arg2 should be 0 or 1 for dbg command\n");
goto error;
}
} else if (arg1 == 0) {
if (arg2 == 1) {
priv->debug_enabled = true;
reg |= BIT(arg1);
} else if (arg2 == 0) {
priv->debug_enabled = false;
reg &= ~BIT(arg1);
} else {
dev_err(dev, "arg2 should be 0 or 1 for dbg command\n");
goto error;
}
} else {
dev_err(dev, "arg1 should be 0 or 1 for dbg command\n");
goto error;
}
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_DEBUG, reg);
} else if (strcmp(cmd, "get") == 0) {
if (!arg1_str || !arg2_str) {
dev_err(dev, "get command requires two arguments\n");
goto error;
}
phyt_get_one_reg(priv, arg1, arg2);
} else if (strcmp(cmd, "set") == 0) {
if (!arg1_str || !arg2_str || !arg3_str) {
dev_err(dev, "set command requires three arguments\n");
goto error;
}
phyt_set_one_reg(priv, arg1, arg2, arg3);
} else if (strcmp(cmd, "dump") == 0) {
if (!arg1_str) {
dev_err(dev, "dump command requires one argument\n");
goto error;
}
memset(priv->sharemem_base, 0, sizeof(struct phytcodec_cmd));
phyt_get_cmd(priv, PHYTCODEC_MSG_CMD_GET_ALL_REGS);
} else if (strcmp(cmd, "help") == 0) {
dev_info(dev, "Available commands:\n"
"dump all regs: echo \"dump\" > debug\n"
"dbg: echo \"dbg 0 1\" > debug\n"
"heartbeat: echo \"dbg 1 1\" > debug\n"
"read a reg: echo \"get [addr] [reg]\" > debug\n"
"write a reg: echo \"set [addr] [reg] [val]\" > debug\n");
} else {
dev_err(dev, "Unknown command: %s\n", cmd);
goto error;
}
kfree(cmd_buffer);
return size;
error:
kfree(cmd_buffer);
return -EINVAL;
}
static DEVICE_ATTR_RW(debug);
static struct attribute *phyt_codec_device_attr[] = {
&dev_attr_debug.attr,
NULL,
};
static const struct attribute_group phyt_codec_device_group = {
.attrs = phyt_codec_device_attr,
};
static int phyt_get_channels(struct phytium_codec *priv)
{
struct phytcodec_cmd *msg = priv->sharemem_base;
int ret = 0;
uint8_t channels;
memset(msg, 0, sizeof(struct phytcodec_cmd));
ret = phyt_get_cmd(priv, PHYTCODEC_MSG_CMD_GET_CHANNELS);
channels = msg->cmd_para.para[0] * 2;
return channels;
}
static void phyt_codec_init(struct phytium_codec *priv)
{
phyt_disable_debug(priv);
phyt_disable_alive(priv);
priv->debug_enabled = false;
priv->alive_enabled = false;
priv->heartbeat = phyt_heartbeat;
priv->timer.expires = jiffies + msecs_to_jiffies(10000);
timer_setup(&priv->timer, phyt_timer_handle, 0);
add_timer(&priv->timer);
if (sysfs_create_group(&priv->dev->kobj, &phyt_codec_device_group))
dev_warn(priv->dev, "failed to create sysfs\n");
priv->channels = phyt_get_channels(priv);
phyt_dai.playback.channels_max = priv->channels;
phyt_dai.capture.channels_max = priv->channels;
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_INT_MASK, 0x0);
phyt_writel_reg(priv->regfile_base, PHYTIUM_CODEC_INT_ENABLE, 0x1);
}
static int phyt_codec_probe(struct platform_device *pdev)
{
struct phytium_codec *priv;
struct resource *res;
int ret;
struct device *dev;
dev = &pdev->dev;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(dev, "failed to alloc struct phytium_codec\n");
ret = -ENOMEM;
goto failed_alloc_phytium_codec;
}
dev_set_drvdata(dev, priv);
priv->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->regfile_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->regfile_base)) {
dev_err(&pdev->dev, "failed to ioremap resource0\n");
ret = PTR_ERR(priv->regfile_base);
goto failed_ioremap_res0;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
priv->sharemem_base = devm_ioremap_wc(&pdev->dev, res->start, resource_size(res));
if (IS_ERR(priv->sharemem_base)) {
dev_err(&pdev->dev, "failed to ioremap resource1\n");
ret = PTR_ERR(priv->sharemem_base);
goto failed_ioremap_res1;
}
priv->regmap = devm_regmap_init_mmio(dev, priv->regfile_base,
&phyt_codec_regmap_config);
if (IS_ERR(priv->regmap)) {
dev_err(dev, "failed to init regmap\n");
ret = PTR_ERR(priv->regmap);
goto failed_regmap_init;
}
phyt_codec_init(priv);
ret = devm_snd_soc_register_component(dev, &phyt_component_driver,
&phyt_dai, 1);
if (ret != 0) {
dev_err(dev, "not able to register codec dai\n");
goto failed_register_com;
}
return 0;
failed_register_com:
failed_regmap_init:
failed_ioremap_res1:
failed_ioremap_res0:
failed_alloc_phytium_codec:
return ret;
}
static int phyt_codec_remove(struct platform_device *pdev)
{
struct phytium_codec *priv = dev_get_drvdata(&pdev->dev);
sysfs_remove_group(&pdev->dev.kobj, &phyt_codec_device_group);
del_timer(&priv->timer);
return 0;
}
static const struct of_device_id phyt_codec_of_match[] = {
{ .compatible = "phytium,codec-2.0", },
{ },
};
MODULE_DEVICE_TABLE(of, phyt_codec_of_match);
static const struct acpi_device_id phyt_codec_acpi_match[] = {
{ "PHYT1002", 0},
{ },
};
MODULE_DEVICE_TABLE(acpi, phyt_codec_acpi_match);
static struct platform_driver phyt_codec_driver = {
.probe = phyt_codec_probe,
.remove = phyt_codec_remove,
.driver = {
.name = "phytium-codec-v2",
.of_match_table = of_match_ptr(phyt_codec_of_match),
.acpi_match_table = phyt_codec_acpi_match,
},
};
module_platform_driver(phyt_codec_driver);
MODULE_DESCRIPTION("Phytium CODEC V2 Driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yang Xun <yangxun@phytium.com.cn>");
MODULE_VERSION(PHYT_CODEC_V2_VERSION);