mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-03 17:40:04 +00:00
![Linus Torvalds](/assets/img/avatar_default.png)
Here is the big set of staging driver updates for 5.19-rc1. Lots of forward progress happened this development cycle, one driver (wfx wireless driver) got merged into the real portion of the kernel, and another one (unisys) was removed as no one is around anymore to take care of it and no one has the hardware. Combined with loads of tiny driver cleanups overall we removed 13k lines of code from the tree, a nice improvement. Other than the wfx and unisys driver changes the major points of this merge is: - r8188eu driver cleanups. So many cleanups. It's amazing just how many things have been cleaned up here, and yet, how many remain to go. Lots of work happened here, and it doesn't look to slow down any time soon. - other wifi driver cleanups. Not as many as the r8188eu driver, but still pretty impressive from a janitorial point of view. - bcm2853 driver cleanups - other very minor driver cleanups All of these have been in the linux-next tree for weeks with no reported issues. Note, you will have a merge conflict in the drivers/net/wireless/silabs/wfx/sta.c file, please just take the change that came in from the wifi tree. We thought as I had pulled the same merge point from the wifi developers this type of conflict wouldn't have happened, but for some reason git flags it as something to pay attention to and couldn't resolve it itself. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCYpnfqA8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ynbFQCfbr7wdJYsNfVd0nXlDUw9EQtbhR8AoM5Y31Ni hBJs6fa/HMGfLnrmN2Xi =BQyT -----END PGP SIGNATURE----- Merge tag 'staging-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging Pull staging driver updates from Greg KH: "Here is the big set of staging driver updates for 5.19-rc1. Lots of forward progress happened this development cycle, one driver (wfx wireless driver) got merged into the real portion of the kernel, and another one (unisys) was removed as no one is around anymore to take care of it and no one has the hardware. Combined with loads of tiny driver cleanups overall we removed 13k lines of code from the tree, a nice improvement. Other than the wfx and unisys driver changes the major points of this merge is: - r8188eu driver cleanups. So many cleanups. It's amazing just how many things have been cleaned up here, and yet, how many remain to go. Lots of work happened here, and it doesn't look to slow down any time soon. - other wifi driver cleanups. Not as many as the r8188eu driver, but still pretty impressive from a janitorial point of view. - bcm2853 driver cleanups - other very minor driver cleanups All of these have been in the linux-next tree for weeks with no reported issues" * tag 'staging-5.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (363 commits) staging: r8188eu: remove include/rtw_debug.h staging: r8188eu: prevent ->Ssid overflow in rtw_wx_set_scan() staging: r8188eu: delete rtw_wx_read/write32() staging: r8188eu: Remove multiple assignments staging: r8188eu: add check for kzalloc staging: r8188eu: fix warnings in rtw_wlan_util staging: r8188eu: fix warnings in rtw_pwrctrl staging: r8188eu: fix warnings in rtw_p2p staging: rtl8712: fix uninit-value in r871xu_drv_init() staging: rtl8712: fix uninit-value in usb_read8() and friends staging: rtl8712: add error handler in r8712_usbctrl_vendorreq() staging: r8188eu: remove _drv_ defines from include/rtw_debug.h staging: vc04_services: remove unused macro staging: rtl8192u: remove null check after call container_of() staging: rtl8192e: remove null check after call container_of() staging: ks7010: remove null check after call container_of() staging: r8188eu: remove HW_VAR_AC_PARAM_BE from SetHwReg8188EU() staging: r8188eu: assoc_rsp and assoc_rsp_len are not used staging: r8188eu: last_rx_mgnt_pkts is set but not used staging: r8188eu: simplify error handling in recv_func_prehandle ...
1104 lines
28 KiB
C
1104 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* APBridge ALSA SoC dummy codec driver
|
|
* Copyright 2016 Google Inc.
|
|
* Copyright 2016 Linaro Ltd.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <uapi/linux/input.h>
|
|
|
|
#include "audio_codec.h"
|
|
#include "audio_apbridgea.h"
|
|
#include "audio_manager.h"
|
|
#include "audio_helper.h"
|
|
|
|
static struct gbaudio_codec_info *gbcodec;
|
|
|
|
static struct gbaudio_data_connection *
|
|
find_data(struct gbaudio_module_info *module, int id)
|
|
{
|
|
struct gbaudio_data_connection *data;
|
|
|
|
list_for_each_entry(data, &module->data_list, list) {
|
|
if (id == data->id)
|
|
return data;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct gbaudio_stream_params *
|
|
find_dai_stream_params(struct gbaudio_codec_info *codec, int id, int stream)
|
|
{
|
|
struct gbaudio_codec_dai *dai;
|
|
|
|
list_for_each_entry(dai, &codec->dai_list, list) {
|
|
if (dai->id == id)
|
|
return &dai->params[stream];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int gbaudio_module_enable_tx(struct gbaudio_codec_info *codec,
|
|
struct gbaudio_module_info *module, int id)
|
|
{
|
|
int module_state, ret = 0;
|
|
u16 data_cport, i2s_port, cportid;
|
|
u8 sig_bits, channels;
|
|
u32 format, rate;
|
|
struct gbaudio_data_connection *data;
|
|
struct gbaudio_stream_params *params;
|
|
|
|
/* find the dai */
|
|
data = find_data(module, id);
|
|
if (!data) {
|
|
dev_err(module->dev, "%d:DATA connection missing\n", id);
|
|
return -ENODEV;
|
|
}
|
|
module_state = data->state[SNDRV_PCM_STREAM_PLAYBACK];
|
|
|
|
params = find_dai_stream_params(codec, id, SNDRV_PCM_STREAM_PLAYBACK);
|
|
if (!params) {
|
|
dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* register cport */
|
|
if (module_state < GBAUDIO_CODEC_STARTUP) {
|
|
i2s_port = 0; /* fixed for now */
|
|
cportid = data->connection->hd_cport_id;
|
|
ret = gb_audio_apbridgea_register_cport(data->connection,
|
|
i2s_port, cportid,
|
|
AUDIO_APBRIDGEA_DIRECTION_TX);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev, "reg_cport failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_STARTUP;
|
|
dev_dbg(module->dev, "Dynamic Register %d DAI\n", cportid);
|
|
}
|
|
|
|
/* hw_params */
|
|
if (module_state < GBAUDIO_CODEC_HWPARAMS) {
|
|
format = params->format;
|
|
channels = params->channels;
|
|
rate = params->rate;
|
|
sig_bits = params->sig_bits;
|
|
data_cport = data->connection->intf_cport_id;
|
|
ret = gb_audio_gb_set_pcm(module->mgmt_connection, data_cport,
|
|
format, rate, channels, sig_bits);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev, "set_pcm failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_HWPARAMS;
|
|
dev_dbg(module->dev, "Dynamic hw_params %d DAI\n", data_cport);
|
|
}
|
|
|
|
/* prepare */
|
|
if (module_state < GBAUDIO_CODEC_PREPARE) {
|
|
data_cport = data->connection->intf_cport_id;
|
|
ret = gb_audio_gb_set_tx_data_size(module->mgmt_connection,
|
|
data_cport, 192);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"set_tx_data_size failed:%d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
ret = gb_audio_gb_activate_tx(module->mgmt_connection, data_cport);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"activate_tx failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_PREPARE;
|
|
dev_dbg(module->dev, "Dynamic prepare %d DAI\n", data_cport);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gbaudio_module_disable_tx(struct gbaudio_module_info *module, int id)
|
|
{
|
|
int ret;
|
|
u16 data_cport, cportid, i2s_port;
|
|
int module_state;
|
|
struct gbaudio_data_connection *data;
|
|
|
|
/* find the dai */
|
|
data = find_data(module, id);
|
|
if (!data) {
|
|
dev_err(module->dev, "%d:DATA connection missing\n", id);
|
|
return -ENODEV;
|
|
}
|
|
module_state = data->state[SNDRV_PCM_STREAM_PLAYBACK];
|
|
|
|
if (module_state > GBAUDIO_CODEC_HWPARAMS) {
|
|
data_cport = data->connection->intf_cport_id;
|
|
ret = gb_audio_gb_deactivate_tx(module->mgmt_connection,
|
|
data_cport);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"deactivate_tx failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
dev_dbg(module->dev, "Dynamic deactivate %d DAI\n", data_cport);
|
|
data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_HWPARAMS;
|
|
}
|
|
|
|
if (module_state > GBAUDIO_CODEC_SHUTDOWN) {
|
|
i2s_port = 0; /* fixed for now */
|
|
cportid = data->connection->hd_cport_id;
|
|
ret = gb_audio_apbridgea_unregister_cport(data->connection,
|
|
i2s_port, cportid,
|
|
AUDIO_APBRIDGEA_DIRECTION_TX);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"unregister_cport failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
dev_dbg(module->dev, "Dynamic Unregister %d DAI\n", cportid);
|
|
data->state[SNDRV_PCM_STREAM_PLAYBACK] = GBAUDIO_CODEC_SHUTDOWN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gbaudio_module_enable_rx(struct gbaudio_codec_info *codec,
|
|
struct gbaudio_module_info *module, int id)
|
|
{
|
|
int module_state, ret = 0;
|
|
u16 data_cport, i2s_port, cportid;
|
|
u8 sig_bits, channels;
|
|
u32 format, rate;
|
|
struct gbaudio_data_connection *data;
|
|
struct gbaudio_stream_params *params;
|
|
|
|
/* find the dai */
|
|
data = find_data(module, id);
|
|
if (!data) {
|
|
dev_err(module->dev, "%d:DATA connection missing\n", id);
|
|
return -ENODEV;
|
|
}
|
|
module_state = data->state[SNDRV_PCM_STREAM_CAPTURE];
|
|
|
|
params = find_dai_stream_params(codec, id, SNDRV_PCM_STREAM_CAPTURE);
|
|
if (!params) {
|
|
dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* register cport */
|
|
if (module_state < GBAUDIO_CODEC_STARTUP) {
|
|
i2s_port = 0; /* fixed for now */
|
|
cportid = data->connection->hd_cport_id;
|
|
ret = gb_audio_apbridgea_register_cport(data->connection,
|
|
i2s_port, cportid,
|
|
AUDIO_APBRIDGEA_DIRECTION_RX);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev, "reg_cport failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_STARTUP;
|
|
dev_dbg(module->dev, "Dynamic Register %d DAI\n", cportid);
|
|
}
|
|
|
|
/* hw_params */
|
|
if (module_state < GBAUDIO_CODEC_HWPARAMS) {
|
|
format = params->format;
|
|
channels = params->channels;
|
|
rate = params->rate;
|
|
sig_bits = params->sig_bits;
|
|
data_cport = data->connection->intf_cport_id;
|
|
ret = gb_audio_gb_set_pcm(module->mgmt_connection, data_cport,
|
|
format, rate, channels, sig_bits);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev, "set_pcm failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_HWPARAMS;
|
|
dev_dbg(module->dev, "Dynamic hw_params %d DAI\n", data_cport);
|
|
}
|
|
|
|
/* prepare */
|
|
if (module_state < GBAUDIO_CODEC_PREPARE) {
|
|
data_cport = data->connection->intf_cport_id;
|
|
ret = gb_audio_gb_set_rx_data_size(module->mgmt_connection,
|
|
data_cport, 192);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"set_rx_data_size failed:%d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
ret = gb_audio_gb_activate_rx(module->mgmt_connection,
|
|
data_cport);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"activate_rx failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_PREPARE;
|
|
dev_dbg(module->dev, "Dynamic prepare %d DAI\n", data_cport);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gbaudio_module_disable_rx(struct gbaudio_module_info *module, int id)
|
|
{
|
|
int ret;
|
|
u16 data_cport, cportid, i2s_port;
|
|
int module_state;
|
|
struct gbaudio_data_connection *data;
|
|
|
|
/* find the dai */
|
|
data = find_data(module, id);
|
|
if (!data) {
|
|
dev_err(module->dev, "%d:DATA connection missing\n", id);
|
|
return -ENODEV;
|
|
}
|
|
module_state = data->state[SNDRV_PCM_STREAM_CAPTURE];
|
|
|
|
if (module_state > GBAUDIO_CODEC_HWPARAMS) {
|
|
data_cport = data->connection->intf_cport_id;
|
|
ret = gb_audio_gb_deactivate_rx(module->mgmt_connection,
|
|
data_cport);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"deactivate_rx failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
dev_dbg(module->dev, "Dynamic deactivate %d DAI\n", data_cport);
|
|
data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_HWPARAMS;
|
|
}
|
|
|
|
if (module_state > GBAUDIO_CODEC_SHUTDOWN) {
|
|
i2s_port = 0; /* fixed for now */
|
|
cportid = data->connection->hd_cport_id;
|
|
ret = gb_audio_apbridgea_unregister_cport(data->connection,
|
|
i2s_port, cportid,
|
|
AUDIO_APBRIDGEA_DIRECTION_RX);
|
|
if (ret) {
|
|
dev_err_ratelimited(module->dev,
|
|
"unregister_cport failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
dev_dbg(module->dev, "Dynamic Unregister %d DAI\n", cportid);
|
|
data->state[SNDRV_PCM_STREAM_CAPTURE] = GBAUDIO_CODEC_SHUTDOWN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gbaudio_module_update(struct gbaudio_codec_info *codec,
|
|
struct snd_soc_dapm_widget *w,
|
|
struct gbaudio_module_info *module, int enable)
|
|
{
|
|
int dai_id, ret;
|
|
char intf_name[NAME_SIZE], dir[NAME_SIZE];
|
|
|
|
dev_dbg(module->dev, "%s:Module update %s sequence\n", w->name,
|
|
enable ? "Enable" : "Disable");
|
|
|
|
if ((w->id != snd_soc_dapm_aif_in) && (w->id != snd_soc_dapm_aif_out)) {
|
|
dev_dbg(codec->dev, "No action required for %s\n", w->name);
|
|
return 0;
|
|
}
|
|
|
|
/* parse dai_id from AIF widget's stream_name */
|
|
ret = sscanf(w->sname, "%s %d %s", intf_name, &dai_id, dir);
|
|
if (ret < 3) {
|
|
dev_err(codec->dev, "Error while parsing dai_id for %s\n", w->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&codec->lock);
|
|
if (w->id == snd_soc_dapm_aif_in) {
|
|
if (enable)
|
|
ret = gbaudio_module_enable_tx(codec, module, dai_id);
|
|
else
|
|
ret = gbaudio_module_disable_tx(module, dai_id);
|
|
} else if (w->id == snd_soc_dapm_aif_out) {
|
|
if (enable)
|
|
ret = gbaudio_module_enable_rx(codec, module, dai_id);
|
|
else
|
|
ret = gbaudio_module_disable_rx(module, dai_id);
|
|
}
|
|
|
|
mutex_unlock(&codec->lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(gbaudio_module_update);
|
|
|
|
/*
|
|
* codec DAI ops
|
|
*/
|
|
static int gbcodec_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
|
|
struct gbaudio_stream_params *params;
|
|
|
|
mutex_lock(&codec->lock);
|
|
|
|
if (list_empty(&codec->module_list)) {
|
|
dev_err(codec->dev, "No codec module available\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
params = find_dai_stream_params(codec, dai->id, substream->stream);
|
|
if (!params) {
|
|
dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
params->state = GBAUDIO_CODEC_STARTUP;
|
|
mutex_unlock(&codec->lock);
|
|
/* to prevent suspend in case of active audio */
|
|
pm_stay_awake(dai->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void gbcodec_shutdown(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
|
|
struct gbaudio_stream_params *params;
|
|
|
|
mutex_lock(&codec->lock);
|
|
|
|
if (list_empty(&codec->module_list))
|
|
dev_info(codec->dev, "No codec module available during shutdown\n");
|
|
|
|
params = find_dai_stream_params(codec, dai->id, substream->stream);
|
|
if (!params) {
|
|
dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
|
|
mutex_unlock(&codec->lock);
|
|
return;
|
|
}
|
|
params->state = GBAUDIO_CODEC_SHUTDOWN;
|
|
mutex_unlock(&codec->lock);
|
|
pm_relax(dai->dev);
|
|
}
|
|
|
|
static int gbcodec_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *hwparams,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
int ret;
|
|
u8 sig_bits, channels;
|
|
u32 format, rate;
|
|
struct gbaudio_module_info *module;
|
|
struct gbaudio_data_connection *data;
|
|
struct gb_bundle *bundle;
|
|
struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
|
|
struct gbaudio_stream_params *params;
|
|
|
|
mutex_lock(&codec->lock);
|
|
|
|
if (list_empty(&codec->module_list)) {
|
|
dev_err(codec->dev, "No codec module available\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* assuming, currently only 48000 Hz, 16BIT_LE, stereo
|
|
* is supported, validate params before configuring codec
|
|
*/
|
|
if (params_channels(hwparams) != 2) {
|
|
dev_err(dai->dev, "Invalid channel count:%d\n",
|
|
params_channels(hwparams));
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
channels = params_channels(hwparams);
|
|
|
|
if (params_rate(hwparams) != 48000) {
|
|
dev_err(dai->dev, "Invalid sampling rate:%d\n",
|
|
params_rate(hwparams));
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
rate = GB_AUDIO_PCM_RATE_48000;
|
|
|
|
if (params_format(hwparams) != SNDRV_PCM_FORMAT_S16_LE) {
|
|
dev_err(dai->dev, "Invalid format:%d\n", params_format(hwparams));
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
format = GB_AUDIO_PCM_FMT_S16_LE;
|
|
|
|
/* find the data connection */
|
|
list_for_each_entry(module, &codec->module_list, list) {
|
|
data = find_data(module, dai->id);
|
|
if (data)
|
|
break;
|
|
}
|
|
|
|
if (!data) {
|
|
dev_err(dai->dev, "DATA connection missing\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
params = find_dai_stream_params(codec, dai->id, substream->stream);
|
|
if (!params) {
|
|
dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
if (ret) {
|
|
mutex_unlock(&codec->lock);
|
|
return ret;
|
|
}
|
|
|
|
ret = gb_audio_apbridgea_set_config(data->connection, 0,
|
|
AUDIO_APBRIDGEA_PCM_FMT_16,
|
|
AUDIO_APBRIDGEA_PCM_RATE_48000,
|
|
6144000);
|
|
if (ret) {
|
|
dev_err_ratelimited(dai->dev, "%d: Error during set_config\n",
|
|
ret);
|
|
gb_pm_runtime_put_noidle(bundle);
|
|
mutex_unlock(&codec->lock);
|
|
return ret;
|
|
}
|
|
|
|
gb_pm_runtime_put_noidle(bundle);
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
sig_bits = dai->driver->playback.sig_bits;
|
|
else
|
|
sig_bits = dai->driver->capture.sig_bits;
|
|
|
|
params->state = GBAUDIO_CODEC_HWPARAMS;
|
|
params->format = format;
|
|
params->rate = rate;
|
|
params->channels = channels;
|
|
params->sig_bits = sig_bits;
|
|
|
|
mutex_unlock(&codec->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int gbcodec_prepare(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
int ret;
|
|
struct gbaudio_module_info *module = NULL, *iter;
|
|
struct gbaudio_data_connection *data;
|
|
struct gb_bundle *bundle;
|
|
struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
|
|
struct gbaudio_stream_params *params;
|
|
|
|
mutex_lock(&codec->lock);
|
|
|
|
if (list_empty(&codec->module_list)) {
|
|
dev_err(codec->dev, "No codec module available\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
list_for_each_entry(iter, &codec->module_list, list) {
|
|
/* find the dai */
|
|
data = find_data(iter, dai->id);
|
|
if (data) {
|
|
module = iter;
|
|
break;
|
|
}
|
|
}
|
|
if (!data) {
|
|
dev_err(dai->dev, "DATA connection missing\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
params = find_dai_stream_params(codec, dai->id, substream->stream);
|
|
if (!params) {
|
|
dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
if (ret) {
|
|
mutex_unlock(&codec->lock);
|
|
return ret;
|
|
}
|
|
|
|
switch (substream->stream) {
|
|
case SNDRV_PCM_STREAM_PLAYBACK:
|
|
ret = gb_audio_apbridgea_set_tx_data_size(data->connection, 0, 192);
|
|
break;
|
|
case SNDRV_PCM_STREAM_CAPTURE:
|
|
ret = gb_audio_apbridgea_set_rx_data_size(data->connection, 0, 192);
|
|
break;
|
|
}
|
|
if (ret) {
|
|
gb_pm_runtime_put_noidle(bundle);
|
|
mutex_unlock(&codec->lock);
|
|
dev_err_ratelimited(dai->dev, "set_data_size failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
gb_pm_runtime_put_noidle(bundle);
|
|
|
|
params->state = GBAUDIO_CODEC_PREPARE;
|
|
mutex_unlock(&codec->lock);
|
|
return 0;
|
|
}
|
|
|
|
static int gbcodec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
|
|
{
|
|
int ret;
|
|
struct gbaudio_data_connection *data;
|
|
struct gbaudio_module_info *module = NULL, *iter;
|
|
struct gb_bundle *bundle;
|
|
struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
|
|
struct gbaudio_stream_params *params;
|
|
|
|
dev_dbg(dai->dev, "Mute:%d, Direction:%s\n", mute,
|
|
stream ? "CAPTURE" : "PLAYBACK");
|
|
|
|
mutex_lock(&codec->lock);
|
|
|
|
params = find_dai_stream_params(codec, dai->id, stream);
|
|
if (!params) {
|
|
dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
|
|
mutex_unlock(&codec->lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (list_empty(&codec->module_list)) {
|
|
dev_err(codec->dev, "No codec module available\n");
|
|
if (mute) {
|
|
params->state = GBAUDIO_CODEC_STOP;
|
|
ret = 0;
|
|
} else {
|
|
ret = -ENODEV;
|
|
}
|
|
mutex_unlock(&codec->lock);
|
|
return ret;
|
|
}
|
|
|
|
list_for_each_entry(iter, &codec->module_list, list) {
|
|
/* find the dai */
|
|
data = find_data(iter, dai->id);
|
|
if (data) {
|
|
module = iter;
|
|
break;
|
|
}
|
|
}
|
|
if (!data) {
|
|
dev_err(dai->dev, "%s DATA connection missing\n",
|
|
dai->name);
|
|
mutex_unlock(&codec->lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
bundle = to_gb_bundle(module->dev);
|
|
ret = gb_pm_runtime_get_sync(bundle);
|
|
if (ret) {
|
|
mutex_unlock(&codec->lock);
|
|
return ret;
|
|
}
|
|
|
|
if (!mute && !stream) {/* start playback */
|
|
ret = gb_audio_apbridgea_prepare_tx(data->connection, 0);
|
|
if (!ret)
|
|
ret = gb_audio_apbridgea_start_tx(data->connection, 0, 0);
|
|
params->state = GBAUDIO_CODEC_START;
|
|
} else if (!mute && stream) {/* start capture */
|
|
ret = gb_audio_apbridgea_prepare_rx(data->connection, 0);
|
|
if (!ret)
|
|
ret = gb_audio_apbridgea_start_rx(data->connection, 0);
|
|
params->state = GBAUDIO_CODEC_START;
|
|
} else if (mute && !stream) {/* stop playback */
|
|
ret = gb_audio_apbridgea_stop_tx(data->connection, 0);
|
|
if (!ret)
|
|
ret = gb_audio_apbridgea_shutdown_tx(data->connection, 0);
|
|
params->state = GBAUDIO_CODEC_STOP;
|
|
} else if (mute && stream) {/* stop capture */
|
|
ret = gb_audio_apbridgea_stop_rx(data->connection, 0);
|
|
if (!ret)
|
|
ret = gb_audio_apbridgea_shutdown_rx(data->connection, 0);
|
|
params->state = GBAUDIO_CODEC_STOP;
|
|
} else {
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
if (ret)
|
|
dev_err_ratelimited(dai->dev,
|
|
"%s:Error during %s %s stream:%d\n",
|
|
module->name, mute ? "Mute" : "Unmute",
|
|
stream ? "Capture" : "Playback", ret);
|
|
|
|
gb_pm_runtime_put_noidle(bundle);
|
|
mutex_unlock(&codec->lock);
|
|
return ret;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops gbcodec_dai_ops = {
|
|
.startup = gbcodec_startup,
|
|
.shutdown = gbcodec_shutdown,
|
|
.hw_params = gbcodec_hw_params,
|
|
.prepare = gbcodec_prepare,
|
|
.mute_stream = gbcodec_mute_stream,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver gbaudio_dai[] = {
|
|
{
|
|
.name = "apb-i2s0",
|
|
.id = 0,
|
|
.playback = {
|
|
.stream_name = "I2S 0 Playback",
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rate_max = 48000,
|
|
.rate_min = 48000,
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.sig_bits = 16,
|
|
},
|
|
.capture = {
|
|
.stream_name = "I2S 0 Capture",
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.rate_max = 48000,
|
|
.rate_min = 48000,
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.sig_bits = 16,
|
|
},
|
|
.ops = &gbcodec_dai_ops,
|
|
},
|
|
};
|
|
|
|
static int gbaudio_init_jack(struct gbaudio_module_info *module,
|
|
struct snd_soc_card *card)
|
|
{
|
|
int ret;
|
|
struct gbaudio_jack *jack, *n;
|
|
struct snd_soc_jack_pin *headset, *button;
|
|
|
|
if (!module->jack_mask)
|
|
return 0;
|
|
|
|
snprintf(module->jack_name, NAME_SIZE, "GB %d Headset Jack",
|
|
module->dev_id);
|
|
|
|
headset = devm_kzalloc(module->dev, sizeof(*headset), GFP_KERNEL);
|
|
if (!headset)
|
|
return -ENOMEM;
|
|
|
|
headset->pin = module->jack_name;
|
|
headset->mask = module->jack_mask;
|
|
ret = snd_soc_card_jack_new_pins(card, module->jack_name,
|
|
module->jack_mask,
|
|
&module->headset.jack, headset, 1);
|
|
if (ret) {
|
|
dev_err(module->dev, "Failed to create new jack\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Add to module's jack list */
|
|
list_add(&module->headset.list, &module->jack_list);
|
|
|
|
if (!module->button_mask)
|
|
return 0;
|
|
|
|
snprintf(module->button_name, NAME_SIZE, "GB %d Button Jack",
|
|
module->dev_id);
|
|
button = devm_kzalloc(module->dev, sizeof(*button), GFP_KERNEL);
|
|
if (!button) {
|
|
ret = -ENOMEM;
|
|
goto free_jacks;
|
|
}
|
|
|
|
button->pin = module->button_name;
|
|
button->mask = module->button_mask;
|
|
ret = snd_soc_card_jack_new_pins(card, module->button_name,
|
|
module->button_mask,
|
|
&module->button.jack,
|
|
button, 1);
|
|
if (ret) {
|
|
dev_err(module->dev, "Failed to create button jack\n");
|
|
goto free_jacks;
|
|
}
|
|
|
|
/* Add to module's jack list */
|
|
list_add(&module->button.list, &module->jack_list);
|
|
|
|
/*
|
|
* Currently, max 4 buttons are supported with following key mapping
|
|
* BTN_0 = KEY_MEDIA
|
|
* BTN_1 = KEY_VOICECOMMAND
|
|
* BTN_2 = KEY_VOLUMEUP
|
|
* BTN_3 = KEY_VOLUMEDOWN
|
|
*/
|
|
|
|
if (module->button_mask & SND_JACK_BTN_0) {
|
|
ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_0,
|
|
KEY_MEDIA);
|
|
if (ret) {
|
|
dev_err(module->dev, "Failed to set BTN_0\n");
|
|
goto free_jacks;
|
|
}
|
|
}
|
|
|
|
if (module->button_mask & SND_JACK_BTN_1) {
|
|
ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_1,
|
|
KEY_VOICECOMMAND);
|
|
if (ret) {
|
|
dev_err(module->dev, "Failed to set BTN_1\n");
|
|
goto free_jacks;
|
|
}
|
|
}
|
|
|
|
if (module->button_mask & SND_JACK_BTN_2) {
|
|
ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_2,
|
|
KEY_VOLUMEUP);
|
|
if (ret) {
|
|
dev_err(module->dev, "Failed to set BTN_2\n");
|
|
goto free_jacks;
|
|
}
|
|
}
|
|
|
|
if (module->button_mask & SND_JACK_BTN_3) {
|
|
ret = snd_jack_set_key(module->button.jack.jack, SND_JACK_BTN_3,
|
|
KEY_VOLUMEDOWN);
|
|
if (ret) {
|
|
dev_err(module->dev, "Failed to set BTN_0\n");
|
|
goto free_jacks;
|
|
}
|
|
}
|
|
|
|
/* FIXME
|
|
* verify if this is really required
|
|
set_bit(INPUT_PROP_NO_DUMMY_RELEASE,
|
|
module->button.jack.jack->input_dev->propbit);
|
|
*/
|
|
|
|
return 0;
|
|
|
|
free_jacks:
|
|
list_for_each_entry_safe(jack, n, &module->jack_list, list) {
|
|
snd_device_free(card->snd_card, jack->jack.jack);
|
|
list_del(&jack->list);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int gbaudio_register_module(struct gbaudio_module_info *module)
|
|
{
|
|
int ret;
|
|
struct snd_soc_component *comp;
|
|
struct snd_card *card;
|
|
struct gbaudio_jack *jack = NULL;
|
|
|
|
if (!gbcodec) {
|
|
dev_err(module->dev, "GB Codec not yet probed\n");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
comp = gbcodec->component;
|
|
card = comp->card->snd_card;
|
|
|
|
down_write(&card->controls_rwsem);
|
|
|
|
if (module->num_dais) {
|
|
dev_err(gbcodec->dev,
|
|
"%d:DAIs not supported via gbcodec driver\n",
|
|
module->num_dais);
|
|
up_write(&card->controls_rwsem);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = gbaudio_init_jack(module, comp->card);
|
|
if (ret) {
|
|
up_write(&card->controls_rwsem);
|
|
return ret;
|
|
}
|
|
|
|
if (module->dapm_widgets)
|
|
snd_soc_dapm_new_controls(&comp->dapm, module->dapm_widgets,
|
|
module->num_dapm_widgets);
|
|
if (module->controls)
|
|
snd_soc_add_component_controls(comp, module->controls,
|
|
module->num_controls);
|
|
if (module->dapm_routes)
|
|
snd_soc_dapm_add_routes(&comp->dapm, module->dapm_routes,
|
|
module->num_dapm_routes);
|
|
|
|
/* card already instantiated, create widgets here only */
|
|
if (comp->card->instantiated) {
|
|
gbaudio_dapm_link_component_dai_widgets(comp->card, &comp->dapm);
|
|
#ifdef CONFIG_SND_JACK
|
|
/*
|
|
* register jack devices for this module
|
|
* from codec->jack_list
|
|
*/
|
|
list_for_each_entry(jack, &module->jack_list, list) {
|
|
snd_device_register(comp->card->snd_card,
|
|
jack->jack.jack);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
mutex_lock(&gbcodec->lock);
|
|
list_add(&module->list, &gbcodec->module_list);
|
|
mutex_unlock(&gbcodec->lock);
|
|
|
|
if (comp->card->instantiated)
|
|
ret = snd_soc_dapm_new_widgets(comp->card);
|
|
dev_dbg(comp->dev, "Registered %s module\n", module->name);
|
|
|
|
up_write(&card->controls_rwsem);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(gbaudio_register_module);
|
|
|
|
static void gbaudio_codec_clean_data_tx(struct gbaudio_data_connection *data)
|
|
{
|
|
u16 i2s_port, cportid;
|
|
int ret;
|
|
|
|
if (list_is_singular(&gbcodec->module_list)) {
|
|
ret = gb_audio_apbridgea_stop_tx(data->connection, 0);
|
|
if (ret)
|
|
return;
|
|
ret = gb_audio_apbridgea_shutdown_tx(data->connection, 0);
|
|
if (ret)
|
|
return;
|
|
}
|
|
i2s_port = 0; /* fixed for now */
|
|
cportid = data->connection->hd_cport_id;
|
|
ret = gb_audio_apbridgea_unregister_cport(data->connection,
|
|
i2s_port, cportid,
|
|
AUDIO_APBRIDGEA_DIRECTION_TX);
|
|
data->state[0] = GBAUDIO_CODEC_SHUTDOWN;
|
|
}
|
|
|
|
static void gbaudio_codec_clean_data_rx(struct gbaudio_data_connection *data)
|
|
{
|
|
u16 i2s_port, cportid;
|
|
int ret;
|
|
|
|
if (list_is_singular(&gbcodec->module_list)) {
|
|
ret = gb_audio_apbridgea_stop_rx(data->connection, 0);
|
|
if (ret)
|
|
return;
|
|
ret = gb_audio_apbridgea_shutdown_rx(data->connection, 0);
|
|
if (ret)
|
|
return;
|
|
}
|
|
i2s_port = 0; /* fixed for now */
|
|
cportid = data->connection->hd_cport_id;
|
|
ret = gb_audio_apbridgea_unregister_cport(data->connection,
|
|
i2s_port, cportid,
|
|
AUDIO_APBRIDGEA_DIRECTION_RX);
|
|
data->state[1] = GBAUDIO_CODEC_SHUTDOWN;
|
|
}
|
|
|
|
static void gbaudio_codec_cleanup(struct gbaudio_module_info *module)
|
|
{
|
|
struct gbaudio_data_connection *data;
|
|
int pb_state, cap_state;
|
|
|
|
dev_dbg(gbcodec->dev, "%s: removed, cleanup APBridge\n", module->name);
|
|
list_for_each_entry(data, &module->data_list, list) {
|
|
pb_state = data->state[0];
|
|
cap_state = data->state[1];
|
|
|
|
if (pb_state > GBAUDIO_CODEC_SHUTDOWN)
|
|
gbaudio_codec_clean_data_tx(data);
|
|
|
|
if (cap_state > GBAUDIO_CODEC_SHUTDOWN)
|
|
gbaudio_codec_clean_data_rx(data);
|
|
}
|
|
}
|
|
|
|
void gbaudio_unregister_module(struct gbaudio_module_info *module)
|
|
{
|
|
struct snd_soc_component *comp = gbcodec->component;
|
|
struct snd_card *card = comp->card->snd_card;
|
|
struct gbaudio_jack *jack, *n;
|
|
int mask;
|
|
|
|
dev_dbg(comp->dev, "Unregister %s module\n", module->name);
|
|
|
|
down_write(&card->controls_rwsem);
|
|
mutex_lock(&gbcodec->lock);
|
|
gbaudio_codec_cleanup(module);
|
|
list_del(&module->list);
|
|
dev_dbg(comp->dev, "Process Unregister %s module\n", module->name);
|
|
mutex_unlock(&gbcodec->lock);
|
|
|
|
#ifdef CONFIG_SND_JACK
|
|
/* free jack devices for this module jack_list */
|
|
list_for_each_entry_safe(jack, n, &module->jack_list, list) {
|
|
if (jack == &module->headset)
|
|
mask = GBCODEC_JACK_MASK;
|
|
else if (jack == &module->button)
|
|
mask = GBCODEC_JACK_BUTTON_MASK;
|
|
else
|
|
mask = 0;
|
|
if (mask) {
|
|
dev_dbg(module->dev, "Report %s removal\n",
|
|
jack->jack.jack->id);
|
|
snd_soc_jack_report(&jack->jack, 0, mask);
|
|
snd_device_free(comp->card->snd_card,
|
|
jack->jack.jack);
|
|
list_del(&jack->list);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (module->dapm_routes) {
|
|
dev_dbg(comp->dev, "Removing %d routes\n",
|
|
module->num_dapm_routes);
|
|
snd_soc_dapm_del_routes(&comp->dapm, module->dapm_routes,
|
|
module->num_dapm_routes);
|
|
}
|
|
if (module->controls) {
|
|
dev_dbg(comp->dev, "Removing %d controls\n",
|
|
module->num_controls);
|
|
/* release control semaphore */
|
|
up_write(&card->controls_rwsem);
|
|
gbaudio_remove_component_controls(comp, module->controls,
|
|
module->num_controls);
|
|
down_write(&card->controls_rwsem);
|
|
}
|
|
if (module->dapm_widgets) {
|
|
dev_dbg(comp->dev, "Removing %d widgets\n",
|
|
module->num_dapm_widgets);
|
|
gbaudio_dapm_free_controls(&comp->dapm, module->dapm_widgets,
|
|
module->num_dapm_widgets);
|
|
}
|
|
|
|
dev_dbg(comp->dev, "Unregistered %s module\n", module->name);
|
|
|
|
up_write(&card->controls_rwsem);
|
|
}
|
|
EXPORT_SYMBOL(gbaudio_unregister_module);
|
|
|
|
/*
|
|
* component driver ops
|
|
*/
|
|
static int gbcodec_probe(struct snd_soc_component *comp)
|
|
{
|
|
int i;
|
|
struct gbaudio_codec_info *info;
|
|
struct gbaudio_codec_dai *dai;
|
|
|
|
info = devm_kzalloc(comp->dev, sizeof(*info), GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
info->dev = comp->dev;
|
|
INIT_LIST_HEAD(&info->module_list);
|
|
mutex_init(&info->lock);
|
|
INIT_LIST_HEAD(&info->dai_list);
|
|
|
|
/* init dai_list used to maintain runtime stream info */
|
|
for (i = 0; i < ARRAY_SIZE(gbaudio_dai); i++) {
|
|
dai = devm_kzalloc(comp->dev, sizeof(*dai), GFP_KERNEL);
|
|
if (!dai)
|
|
return -ENOMEM;
|
|
dai->id = gbaudio_dai[i].id;
|
|
list_add(&dai->list, &info->dai_list);
|
|
}
|
|
|
|
info->component = comp;
|
|
snd_soc_component_set_drvdata(comp, info);
|
|
gbcodec = info;
|
|
|
|
device_init_wakeup(comp->dev, 1);
|
|
return 0;
|
|
}
|
|
|
|
static int gbcodec_write(struct snd_soc_component *comp, unsigned int reg,
|
|
unsigned int value)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int gbcodec_read(struct snd_soc_component *comp,
|
|
unsigned int reg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_component_driver soc_codec_dev_gbaudio = {
|
|
.probe = gbcodec_probe,
|
|
.read = gbcodec_read,
|
|
.write = gbcodec_write,
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static int gbaudio_codec_suspend(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "%s: suspend\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int gbaudio_codec_resume(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "%s: resume\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops gbaudio_codec_pm_ops = {
|
|
.suspend = gbaudio_codec_suspend,
|
|
.resume = gbaudio_codec_resume,
|
|
};
|
|
#endif
|
|
|
|
static int gbaudio_codec_probe(struct platform_device *pdev)
|
|
{
|
|
return devm_snd_soc_register_component(&pdev->dev,
|
|
&soc_codec_dev_gbaudio,
|
|
gbaudio_dai, ARRAY_SIZE(gbaudio_dai));
|
|
}
|
|
|
|
static int gbaudio_codec_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id greybus_asoc_machine_of_match[] = {
|
|
{ .compatible = "toshiba,apb-dummy-codec", },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver gbaudio_codec_driver = {
|
|
.driver = {
|
|
.name = "apb-dummy-codec",
|
|
#ifdef CONFIG_PM
|
|
.pm = &gbaudio_codec_pm_ops,
|
|
#endif
|
|
.of_match_table = greybus_asoc_machine_of_match,
|
|
},
|
|
.probe = gbaudio_codec_probe,
|
|
.remove = gbaudio_codec_remove,
|
|
};
|
|
module_platform_driver(gbaudio_codec_driver);
|
|
|
|
MODULE_DESCRIPTION("APBridge ALSA SoC dummy codec driver");
|
|
MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:apb-dummy-codec");
|