linux-stable/drivers/staging/greybus/audio_codec.c
Linus Torvalds 4ad680f083 Staging driver updates for 5.19-rc1
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
  ...
2022-06-03 10:44:43 -07:00

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");