691 lines
16 KiB
C
691 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/**
|
|
* Copyright (C) Fourier Semiconductor Inc. 2016-2020. All rights reserved.
|
|
* 2018-10-22 File created.
|
|
*/
|
|
|
|
#if defined(CONFIG_FSM_CODEC)
|
|
#include "fsm_public.h"
|
|
#include <linux/regmap.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/mutex.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tlv.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/version.h>
|
|
|
|
/* use hrtimer for bsg monitor */
|
|
#define FSM_USE_HRTIMER
|
|
#define FSM_MONITOR_PEROID (3000) // ms
|
|
|
|
struct fsm_codec {
|
|
#ifdef FSM_USE_HRTIMER
|
|
struct hrtimer timer;
|
|
struct work_struct monitor_work;
|
|
atomic_t running;
|
|
#else
|
|
struct workqueue_struct *codec_wq;
|
|
struct delayed_work codec_monitor;
|
|
#endif
|
|
};
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0))
|
|
#define snd_soc_codec snd_soc_component
|
|
#define snd_soc_add_codec_controls snd_soc_add_component_controls
|
|
#define snd_soc_register_codec snd_soc_register_component
|
|
#define snd_soc_unregister_codec snd_soc_unregister_component
|
|
#define snd_soc_codec_driver snd_soc_component_driver
|
|
#define remove_ret_type void
|
|
#define remove_ret_val
|
|
#else
|
|
#define remove_ret_type int
|
|
#define remove_ret_val (0)
|
|
#endif
|
|
|
|
/* Supported rates and data formats */
|
|
#define FSM_RATES SNDRV_PCM_RATE_8000_48000
|
|
#define FSM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE \
|
|
| SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_3LE)
|
|
|
|
static const unsigned int fsm_rates[] = { 8000, 16000, 32000, 44100, 48000 };
|
|
static const struct snd_pcm_hw_constraint_list fsm_constraints = {
|
|
.list = fsm_rates,
|
|
.count = ARRAY_SIZE(fsm_rates),
|
|
};
|
|
struct fsm_codec *g_fsm_codec;
|
|
|
|
static int fsm_get_scene_index(uint16_t scene)
|
|
{
|
|
int index = 0;
|
|
|
|
while (scene) {
|
|
scene = (scene >> 1);
|
|
if (scene == 0) {
|
|
break;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
int fsm_scene_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
int scene_index;
|
|
|
|
if (!cfg) {
|
|
ucontrol->value.integer.value[0] = -1;
|
|
return 0;
|
|
}
|
|
scene_index = fsm_get_scene_index(cfg->next_scene);
|
|
pr_info("next_scene: %04X, BIT(%d)", cfg->next_scene, scene_index);
|
|
ucontrol->value.integer.value[0] = scene_index;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_scene_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
int next_scene = ucontrol->value.integer.value[0];
|
|
|
|
pr_info("next_scene: %d", next_scene);
|
|
if (next_scene < 0 || next_scene >= FSM_SCENE_MAX) {
|
|
next_scene = 0;
|
|
}
|
|
fsm_set_scene(next_scene);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_volume_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
int volume;
|
|
|
|
volume = ((cfg != NULL) ? cfg->volume : FSM_VOLUME_MAX);
|
|
ucontrol->value.integer.value[0] = volume;
|
|
pr_info("volume: %ld", ucontrol->value.integer.value[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_volume_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
int volume = ucontrol->value.integer.value[0];
|
|
|
|
pr_info("volume: %d", volume);
|
|
fsm_set_volume(volume);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_stop_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
|
|
ucontrol->value.integer.value[0] = ((cfg != NULL) ? cfg->force_mute : 1);
|
|
pr_info("stop: %ld", ucontrol->value.integer.value[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_stop_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
int stop = ucontrol->value.integer.value[0];
|
|
|
|
pr_info("stop: %x", stop);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_rotation_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
|
|
ucontrol->value.integer.value[0] = ((cfg != NULL) ? cfg->cur_angle : 0);
|
|
pr_info("angle: %ld", ucontrol->value.integer.value[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_rotation_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
int angle = ucontrol->value.integer.value[0];
|
|
|
|
pr_info("angle: %x", angle);
|
|
fsm_stereo_flip(angle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_monitor_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
int status;
|
|
|
|
if (cfg == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
status = (!cfg->skip_monitor && cfg->use_monitor);
|
|
pr_info("status: %d", status);
|
|
ucontrol->value.integer.value[0] = status;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_monitor_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
int monitor_on = ucontrol->value.integer.value[0];
|
|
|
|
if (cfg == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
monitor_on = !!monitor_on;
|
|
pr_info("set monitor: %s", monitor_on ? "On" : "Off");
|
|
cfg->use_monitor = monitor_on;
|
|
cfg->skip_monitor = !monitor_on;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_init_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
int state;
|
|
|
|
state = (fsm_get_presets() != NULL) ? 1 : 0;
|
|
pr_info("state:%d", state);
|
|
ucontrol->value.integer.value[0] = state;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_init_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
fsm_init();
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
static int fsm_show_firmware_status(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
//ucontrol->value.integer.value[0] = g_fs16xx_firmware_status;
|
|
//pr_info("g_fs16xx_firmware_status=%d\n", g_fs16xx_firmware_status);
|
|
return 0;
|
|
}
|
|
|
|
static int fsm_load_firmware(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
int ret = 0;
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
pr_info("%s\n enter\n", __func__);
|
|
|
|
ret = fsm_firmware_init(cfg->fw_name);
|
|
if (ret) {
|
|
// firmware init fail, it will try in speaker on again
|
|
pr_info("firmware init fail:%d", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static const struct snd_kcontrol_new fsm_snd_controls[] = {
|
|
SOC_SINGLE_EXT("FSM_Init", SND_SOC_NOPM, 0, 1, 0,
|
|
fsm_init_get, fsm_init_put),
|
|
SOC_SINGLE_EXT("FSM_Scene", SND_SOC_NOPM, 0, FSM_SCENE_MAX, 0,
|
|
fsm_scene_get, fsm_scene_put),
|
|
//SOC_SINGLE_EXT("FSM Firmware", 0, 0, 2, 0,
|
|
// fsm_show_firmware_status, fsm_load_firmware),
|
|
SOC_SINGLE_EXT("FSM_Volume", SND_SOC_NOPM, 0, FSM_VOLUME_MAX, 0,
|
|
fsm_volume_get, fsm_volume_put),
|
|
SOC_SINGLE_EXT("FSM_Stop", SND_SOC_NOPM, 0, 1, 0,
|
|
fsm_stop_get, fsm_stop_put),
|
|
SOC_SINGLE_EXT("FSM_Rotation", SND_SOC_NOPM, 0, 360, 0,
|
|
fsm_rotation_get, fsm_rotation_put),
|
|
SOC_SINGLE_EXT("FSM_Monitor", SND_SOC_NOPM, 0, 1, 0,
|
|
fsm_monitor_get, fsm_monitor_put),
|
|
};
|
|
|
|
static struct snd_soc_dapm_widget fsm_dapm_widgets_common[] = {
|
|
/* Stream widgets */
|
|
SND_SOC_DAPM_AIF_IN("AIF IN", "AIF Playback", 0, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_AIF_OUT("AIF OUT", "AIF Capture", 0, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_OUTPUT("OUTL"),
|
|
SND_SOC_DAPM_INPUT("AEC Loopback"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route fsm_dapm_routes_common[] = {
|
|
{ "OUTL", NULL, "AIF IN" },
|
|
{ "AIF OUT", NULL, "AEC Loopback" },
|
|
};
|
|
|
|
static struct snd_soc_dapm_context *snd_soc_fsm_get_dapm(
|
|
struct snd_soc_codec *codec)
|
|
{
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0))
|
|
return &codec->dapm;
|
|
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 0))
|
|
return snd_soc_codec_get_dapm(codec);
|
|
#else
|
|
return snd_soc_component_get_dapm(codec);
|
|
#endif
|
|
}
|
|
|
|
static int fsm_add_widgets(struct snd_soc_codec *codec)
|
|
{
|
|
struct snd_soc_dapm_context *dapm = snd_soc_fsm_get_dapm(codec);
|
|
|
|
snd_soc_add_codec_controls(codec, fsm_snd_controls,
|
|
ARRAY_SIZE(fsm_snd_controls));
|
|
snd_soc_dapm_new_controls(dapm, fsm_dapm_widgets_common,
|
|
ARRAY_SIZE(fsm_dapm_widgets_common));
|
|
snd_soc_dapm_add_routes(dapm, fsm_dapm_routes_common,
|
|
ARRAY_SIZE(fsm_dapm_routes_common));
|
|
return 0;
|
|
}
|
|
|
|
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,16,0)
|
|
//static struct snd_soc_codec *snd_soc_kcontrol_codec(
|
|
// struct snd_kcontrol *kcontrol)
|
|
//{
|
|
// return snd_kcontrol_chip(kcontrol);
|
|
//}
|
|
//#endif
|
|
|
|
static int fsm_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
int ret;
|
|
|
|
if (!substream->runtime) {
|
|
return 0;
|
|
}
|
|
|
|
ret = snd_pcm_hw_constraint_mask64(substream->runtime, \
|
|
SNDRV_PCM_HW_PARAM_FORMAT, FSM_FORMATS);
|
|
if (ret < 0) {
|
|
pr_info("set pcm param format fail:%d", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_RATE,
|
|
&fsm_constraints);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void fsm_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
}
|
|
|
|
static int fsm_set_sysclk(struct snd_soc_dai *codec_dai,
|
|
int clk_id, unsigned int freq, int dir)
|
|
{
|
|
pr_info("freq:%d", freq);
|
|
return 0;
|
|
}
|
|
|
|
static int fsm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
{
|
|
int format = 0;
|
|
int ret = 0;
|
|
|
|
pr_debug("fmt: %X", fmt);
|
|
/*switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
break;
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
default:
|
|
// only supports Slave mode
|
|
pr_info("invalid DAI master/slave interface");
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_I2S:
|
|
format = 3;
|
|
break;
|
|
default:
|
|
pr_info("invalid dai format: %x", fmt);
|
|
ret = -EINVAL;
|
|
break;
|
|
}*/
|
|
pr_info("format:%d, ret:%d", format, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fsm_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
int bclk;
|
|
int srate;
|
|
int format;
|
|
int sample_size;
|
|
int phy_size;
|
|
|
|
format = params_format(params);
|
|
pr_debug("format:%X", format);
|
|
/*switch (format)
|
|
{
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
case SNDRV_PCM_FORMAT_S24_3LE:
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
break;
|
|
}*/
|
|
|
|
srate = params_rate(params);
|
|
sample_size = snd_pcm_format_width(format);
|
|
phy_size = snd_pcm_format_physical_width(format);
|
|
bclk = srate * phy_size * 2;
|
|
fsm_set_i2s_clocks(srate, bclk);
|
|
pr_info("bclk:%d, srate:%d, fmt:%d, phy:%d", bclk, srate,
|
|
sample_size, phy_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef FSM_USE_HRTIMER
|
|
static enum hrtimer_restart fsm_timer_callback(struct hrtimer *timer)
|
|
{
|
|
struct fsm_codec *fsm_codec = g_fsm_codec;
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
|
|
if (!fsm_codec || !cfg) {
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
if (!atomic_read(&fsm_codec->running)) {
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
if (cfg->use_monitor && !cfg->skip_monitor) {
|
|
schedule_work(&fsm_codec->monitor_work);
|
|
}
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static int fsm_set_timer(struct fsm_codec *fsm_codec, bool enable)
|
|
{
|
|
if (!fsm_codec) {
|
|
return -EINVAL;
|
|
}
|
|
atomic_set(&fsm_codec->running, 0);
|
|
hrtimer_cancel(&fsm_codec->timer);
|
|
if (enable) {
|
|
hrtimer_start(&fsm_codec->timer,
|
|
ktime_set(FSM_MONITOR_PEROID / 1000,
|
|
(FSM_MONITOR_PEROID % 1000) * 1000000),
|
|
HRTIMER_MODE_REL);
|
|
atomic_set(&fsm_codec->running, 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsm_set_codec_monitor(struct fsm_codec *fsm_codec, bool enable)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
if (!cfg || !fsm_codec) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cfg->use_monitor || !cfg->speaker_on) {
|
|
return 0;
|
|
}
|
|
if (enable) {
|
|
cfg->skip_monitor = false;
|
|
#ifdef FSM_USE_HRTIMER
|
|
fsm_set_timer(fsm_codec, true);
|
|
#else
|
|
if (fsm_codec->codec_wq) {
|
|
queue_delayed_work(fsm_codec->codec_wq,
|
|
&fsm_codec->codec_monitor, 5*HZ);
|
|
}
|
|
#endif
|
|
} else {
|
|
cfg->skip_monitor = true;
|
|
#ifdef FSM_USE_HRTIMER
|
|
fsm_set_timer(fsm_codec, false);
|
|
#else
|
|
if (delayed_work_pending(&fsm_codec->codec_monitor)) {
|
|
cancel_delayed_work_sync(&fsm_codec->codec_monitor);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void fsm_codec_monitor(struct work_struct *work)
|
|
{
|
|
struct fsm_codec *fsm_codec = g_fsm_codec;
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
|
|
if (!fsm_codec || !cfg) {
|
|
return;
|
|
}
|
|
if (!cfg->speaker_on || cfg->skip_monitor) {
|
|
return;
|
|
}
|
|
fsm_batv_monitor();
|
|
/* reschedule */
|
|
#ifdef FSM_USE_HRTIMER
|
|
fsm_set_timer(fsm_codec, true);
|
|
#else
|
|
if (fsm_codec->codec_wq) {
|
|
queue_delayed_work(fsm_codec->codec_wq, &fsm_codec->codec_monitor,
|
|
3*HZ);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int fsm_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
|
|
{
|
|
// struct fsm_codec *fsm_codec = g_fsm_codec;
|
|
|
|
if (stream != SNDRV_PCM_STREAM_PLAYBACK) {
|
|
pr_info("captrue stream");
|
|
return 0;
|
|
}
|
|
|
|
if (mute) {
|
|
// fsm_set_codec_monitor(fsm_codec, false);
|
|
fsm_speaker_off();
|
|
} else {
|
|
fsm_speaker_onn();
|
|
// fsm_set_codec_monitor(fsm_codec, true);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef FSM_UNUSED_CODE
|
|
static int fsm_digital_mute(struct snd_soc_dai *dai, int mute)
|
|
{
|
|
return fsm_mute_stream(dai, mute, SNDRV_PCM_STREAM_PLAYBACK);
|
|
}
|
|
|
|
static int fsm_trigger(struct snd_pcm_substream *substream,
|
|
int cmd, struct snd_soc_dai *dai)
|
|
{
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct snd_soc_dai_ops fsm_aif_dai_ops = {
|
|
.startup = fsm_startup,
|
|
.set_fmt = fsm_set_fmt,
|
|
.set_sysclk = fsm_set_sysclk,
|
|
.hw_params = fsm_hw_params,
|
|
.mute_stream = fsm_mute_stream,
|
|
//.digital_mute = fsm_digital_mute,
|
|
//.trigger = fsm_trigger,
|
|
.shutdown = fsm_shutdown,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver fsm_aif_dai[] = {
|
|
{
|
|
.name = "fs16xx-aif",
|
|
.id = 1,
|
|
.playback = {
|
|
.stream_name = "AIF Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rates = FSM_RATES,
|
|
.formats = FSM_FORMATS,
|
|
},
|
|
.capture = {
|
|
.stream_name = "AIF Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 4,
|
|
.rates = FSM_RATES,
|
|
.formats = FSM_FORMATS,
|
|
},
|
|
.ops = &fsm_aif_dai_ops,
|
|
.symmetric_rates = 1,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)
|
|
.symmetric_channels = 1,
|
|
.symmetric_samplebits = 1,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static int fsm_codec_probe(struct snd_soc_codec *codec)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
struct fsm_codec *fsm_codec;
|
|
int ret;
|
|
|
|
pr_info("dev_name: %s", dev_name(codec->dev));
|
|
if (!cfg) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
fsm_set_pdev(codec->dev);
|
|
ret = fsm_add_widgets(codec);
|
|
fsm_codec = devm_kzalloc(codec->dev, sizeof(struct fsm_codec), GFP_KERNEL);
|
|
if (!fsm_codec) {
|
|
pr_info("allocate memory fail");
|
|
return -EINVAL;
|
|
}
|
|
#ifdef FSM_USE_HRTIMER
|
|
hrtimer_init(&fsm_codec->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
fsm_codec->timer.function = fsm_timer_callback;
|
|
INIT_WORK(&fsm_codec->monitor_work, fsm_codec_monitor);
|
|
atomic_set(&fsm_codec->running, 0);
|
|
#else
|
|
fsm_codec->codec_wq = create_singlethread_workqueue("fs16xx");
|
|
INIT_DELAYED_WORK(&fsm_codec->codec_monitor, fsm_codec_monitor);
|
|
#endif
|
|
g_fsm_codec = fsm_codec;
|
|
pr_info("codec registered");
|
|
|
|
FSM_FUNC_EXIT(ret);
|
|
return ret;
|
|
}
|
|
|
|
static remove_ret_type fsm_codec_remove(struct snd_soc_codec *codec)
|
|
{
|
|
struct fsm_codec *fsm_codec = g_fsm_codec;
|
|
|
|
if (fsm_codec) {
|
|
#ifdef FSM_USE_HRTIMER
|
|
fsm_set_timer(fsm_codec, false);
|
|
#else
|
|
cancel_delayed_work_sync(&fsm_codec->codec_monitor);
|
|
destroy_workqueue(fsm_codec->codec_wq);
|
|
#endif
|
|
devm_kfree(codec->dev, fsm_codec);
|
|
}
|
|
if (codec && fsm_get_pdev() == codec->dev) {
|
|
fsm_firmware_deinit();
|
|
}
|
|
return remove_ret_val;
|
|
}
|
|
|
|
static struct snd_soc_codec_driver soc_codec_dev_fsm = {
|
|
.probe = fsm_codec_probe,
|
|
.remove = fsm_codec_remove,
|
|
};
|
|
|
|
int fsm_codec_register(struct device *dev, int id)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
int size = ARRAY_SIZE(fsm_aif_dai);
|
|
int ret;
|
|
|
|
if (!cfg || cfg->codec_inited >= size) {
|
|
// not support codec or codec inited
|
|
return MODULE_INITED;
|
|
}
|
|
pr_info("id:%d, size:%d", id, size);
|
|
if (id < 0 || id >= size) {
|
|
pr_info("invalid id: %d", id);
|
|
return -EINVAL;
|
|
}
|
|
ret = snd_soc_register_codec(dev, &soc_codec_dev_fsm,
|
|
&fsm_aif_dai[id], 1);
|
|
if (ret < 0) {
|
|
dev_info(dev, "failed to register CODEC DAI: %d", ret);
|
|
return ret;
|
|
}
|
|
cfg->codec_inited++;
|
|
// fsm_set_codec_config(dev, &fsm_aif_dai[id], id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void fsm_codec_unregister(struct device *dev)
|
|
{
|
|
fsm_config_t *cfg = fsm_get_config();
|
|
|
|
pr_info("enter");
|
|
if (!cfg || !cfg->codec_inited) {
|
|
return;
|
|
}
|
|
snd_soc_unregister_codec(dev);
|
|
cfg->codec_inited--;
|
|
}
|
|
#endif
|