Add support for BT/FM SoundWire slave driver
Added support for BT/FM SoundWire slave driver. The driver registers as a codec driver with ALSA. Slave port configuration is sent to master driver. Master driver takes care of programming master and slave registers at once when BT audio usecase is started/stopped. Change-Id: I3a4de9872323c470f2ec73218601be94a768d726 Signed-off-by: Satish Kumar Kodishala <quic_skodisha@quicinc.com>
This commit is contained in:
12
soundwire/Kconfig
Normal file
12
soundwire/Kconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
config BTFM_SWR
|
||||
tristate "MSM Bluetooth/FM SoundWire Device"
|
||||
depends on MSM_BT_POWER
|
||||
help
|
||||
This enables BT/FM soundwire driver to open/close ports.
|
||||
This will make use of soundwire bus driver and soundwire
|
||||
master driver to communicate with soundwire slave.
|
||||
|
||||
Say Y here to compile support for Bluetooth/FM soundwire slave driver
|
||||
into the kernel or say M to compile as a module.
|
||||
|
3
soundwire/Makefile
Normal file
3
soundwire/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
ccflags-y += -I$(BT_ROOT)/include
|
||||
bt_fm_swr-objs := btfm_swr.o btfm_swr_codec.o btfm_swr_slave.o
|
||||
obj-$(CONFIG_BTFM_SWR) += bt_fm_swr.o
|
288
soundwire/btfm_swr.c
Normal file
288
soundwire/btfm_swr.c
Normal file
@@ -0,0 +1,288 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ratelimit.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fs.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/tlv.h>
|
||||
#include "btpower.h"
|
||||
#include "btfm_swr.h"
|
||||
#include "btfm_swr_slave.h"
|
||||
|
||||
struct class *btfm_swr_class;
|
||||
static int btfm_swr_major;
|
||||
struct btfmswr *pbtfmswr;
|
||||
static int btfm_num_ports_open;
|
||||
|
||||
#define BT_CMD_SWR_TEST 0xbfac
|
||||
|
||||
static int btfm_swr_probe(struct swr_device *pdev);
|
||||
|
||||
int btfm_get_bt_soc_index(int chipset_ver)
|
||||
{
|
||||
switch (chipset_ver) {
|
||||
case QCA_GANGES_SOC_ID_0100:
|
||||
case QCA_GANGES_SOC_ID_0200:
|
||||
return GANGES;
|
||||
case QCA_EVROS_SOC_ID_0100:
|
||||
case QCA_EVROS_SOC_ID_0200:
|
||||
return EVROS;
|
||||
default:
|
||||
BTFMSWR_ERR("no BT SOC id defined, returning EVROS");
|
||||
return EVROS;
|
||||
}
|
||||
}
|
||||
|
||||
int btfm_swr_hw_init(void)
|
||||
{
|
||||
uint8_t dev_num = 0;
|
||||
int ret = 0;
|
||||
int chipset_ver;
|
||||
|
||||
BTFMSWR_DBG("");
|
||||
|
||||
if (pbtfmswr->initialized)
|
||||
BTFMSWR_INFO("Already initialized");
|
||||
|
||||
// get BT chipset version
|
||||
chipset_ver = btpower_get_chipset_version();
|
||||
|
||||
// get BT/FM SOC slave port details
|
||||
pbtfmswr->soc_index = btfm_get_bt_soc_index(chipset_ver);
|
||||
|
||||
BTFMSWR_INFO("chipset soc version:%x, soc index: %x", chipset_ver,
|
||||
pbtfmswr->soc_index);
|
||||
|
||||
pbtfmswr->p_dai_port = &slave_port[pbtfmswr->soc_index];
|
||||
|
||||
// get logical address
|
||||
/*
|
||||
* Add 5msec delay to provide sufficient time for
|
||||
* soundwire auto enumeration of slave devices as
|
||||
* per HW requirement.
|
||||
*/
|
||||
usleep_range(5000, 5010);
|
||||
ret = swr_get_logical_dev_num(pbtfmswr->swr_slave, pbtfmswr->p_dai_port->ea,
|
||||
&dev_num);
|
||||
if (ret) {
|
||||
BTFMSWR_ERR("error while getting logical device number");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pbtfmswr->swr_slave->dev_num = dev_num;
|
||||
pbtfmswr->initialized = true;
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btfm_swr_enable_port(u8 port_num, u8 ch_count, u32 sample_rate, u8 usecase)
|
||||
{
|
||||
int ret = 0;
|
||||
u8 port_id[MAX_BT_PORTS];
|
||||
u8 num_ch[MAX_BT_PORTS];
|
||||
u8 ch_mask[MAX_BT_PORTS];
|
||||
u32 ch_rate[MAX_BT_PORTS];
|
||||
u8 port_type[MAX_BT_PORTS];
|
||||
u8 num_port = 1;
|
||||
|
||||
// master expects port num -1 to be sent
|
||||
port_id[0] = port_num-1;
|
||||
num_ch[0] = ch_count;
|
||||
ch_mask[0] = ch_count == 2 ? TWO_CHANNEL_MASK : ONE_CHANNEL_MASK;
|
||||
ch_rate[0] = sample_rate;
|
||||
port_type[0] = usecase;
|
||||
|
||||
BTFMSWR_INFO("enabling port : %d\n", port_num);
|
||||
ret = swr_connect_port(pbtfmswr->swr_slave, &port_id[0], num_port,
|
||||
&ch_mask[0], &ch_rate[0], &num_ch[0],
|
||||
&port_type[0]);
|
||||
|
||||
if (ret < 0) {
|
||||
BTFMSWR_ERR("swr_connect_port failed, error %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
BTFMSWR_INFO("calling swr_slvdev_datapath_control\n");
|
||||
ret = swr_slvdev_datapath_control(pbtfmswr->swr_slave,
|
||||
pbtfmswr->swr_slave->dev_num,
|
||||
true);
|
||||
if (ret < 0)
|
||||
BTFMSWR_ERR("swr_slvdev_datapath_control failed");
|
||||
|
||||
if (ret == 0)
|
||||
btfm_num_ports_open++;
|
||||
|
||||
BTFMSWR_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int btfm_swr_disable_port(u8 port_num, u8 ch_count, u8 usecase)
|
||||
{
|
||||
int ret = 0;
|
||||
u8 port_id[MAX_BT_PORTS];
|
||||
u8 ch_mask[MAX_BT_PORTS];
|
||||
u8 port_type[MAX_BT_PORTS];
|
||||
u8 num_port = 1;
|
||||
|
||||
// master expects port num -1 to be sent
|
||||
port_id[0] = port_num-1;
|
||||
ch_mask[0] = ch_count == 2 ? TWO_CHANNEL_MASK : ONE_CHANNEL_MASK;
|
||||
port_type[0] = usecase;
|
||||
|
||||
BTFMSWR_INFO("disabling port : %d\n", port_num);
|
||||
ret = swr_disconnect_port(pbtfmswr->swr_slave, &port_id[0], num_port,
|
||||
&ch_mask[0], &port_type[0]);
|
||||
|
||||
if (ret < 0)
|
||||
BTFMSWR_ERR("swr_disconnect_port port failed, error %d", ret);
|
||||
|
||||
BTFMSWR_INFO("calling swr_slvdev_datapath_control\n");
|
||||
ret = swr_slvdev_datapath_control(pbtfmswr->swr_slave,
|
||||
pbtfmswr->swr_slave->dev_num,
|
||||
false);
|
||||
if (ret < 0)
|
||||
BTFMSWR_ERR("swr_slvdev_datapath_control failed");
|
||||
|
||||
if (btfm_num_ports_open > 0)
|
||||
btfm_num_ports_open--;
|
||||
|
||||
BTFMSWR_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long btfm_swr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
BTFMSWR_INFO("");
|
||||
switch (cmd) {
|
||||
case BT_CMD_SWR_TEST:
|
||||
BTFMSWR_INFO("cmd BT_CMD_SLIM_TEST, call btfm_swr_hw_init");
|
||||
ret = btfm_swr_hw_init();
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations bt_dev_fops = {
|
||||
.unlocked_ioctl = btfm_swr_ioctl,
|
||||
.compat_ioctl = btfm_swr_ioctl,
|
||||
};
|
||||
|
||||
static int btfm_swr_probe(struct swr_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
BTFMSWR_INFO("");
|
||||
|
||||
pbtfmswr = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct btfmswr), GFP_KERNEL);
|
||||
if (!pbtfmswr) {
|
||||
BTFMSWR_ERR("memory allocation to driver failed");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
swr_set_dev_data(pdev, pbtfmswr);
|
||||
pbtfmswr->swr_slave = pdev;
|
||||
pbtfmswr->dev = &pdev->dev;
|
||||
pbtfmswr->initialized = false;
|
||||
|
||||
// register with ALSA
|
||||
ret = btfm_swr_register_codec(pbtfmswr);
|
||||
if (ret) {
|
||||
BTFMSWR_ERR("registration with ALSA failed, returning");
|
||||
goto dealloc;
|
||||
}
|
||||
|
||||
btfm_swr_major = register_chrdev(0, "btfm_swr", &bt_dev_fops);
|
||||
if (btfm_swr_major < 0) {
|
||||
BTFMSWR_ERR("%s: failed to allocate char dev\n", __func__);
|
||||
ret = -1;
|
||||
goto register_err;
|
||||
}
|
||||
|
||||
btfm_swr_class = class_create(THIS_MODULE, "btfmswr-dev");
|
||||
if (IS_ERR(btfm_swr_class)) {
|
||||
BTFMSWR_ERR("%s: coudn't create class\n", __func__);
|
||||
ret = -1;
|
||||
goto class_err;
|
||||
}
|
||||
|
||||
if (device_create(btfm_swr_class, NULL, MKDEV(btfm_swr_major, 0),
|
||||
NULL, "btfmswr") == NULL) {
|
||||
BTFMSWR_ERR("%s: failed to allocate char dev\n", __func__);
|
||||
ret = -1;
|
||||
goto device_err;
|
||||
}
|
||||
return ret;
|
||||
|
||||
device_err:
|
||||
class_destroy(btfm_swr_class);
|
||||
class_err:
|
||||
unregister_chrdev(btfm_swr_major, "btfm_swr");
|
||||
|
||||
register_err:
|
||||
btfm_swr_unregister_codec(pbtfmswr->dev);
|
||||
|
||||
dealloc:
|
||||
kfree(pbtfmswr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct swr_device_id btfm_swr_id[] = {
|
||||
{SWR_SLAVE_COMPATIBLE_STR, 0},
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct of_device_id btfm_swr_dt_match[] = {
|
||||
{
|
||||
.compatible = "qcom,btfmswr_slave",
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct swr_driver btfm_swr_driver = {
|
||||
.driver = {
|
||||
.name = "btfmswr-driver",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = btfm_swr_dt_match,
|
||||
},
|
||||
.probe = btfm_swr_probe,
|
||||
.id_table = btfm_swr_id,
|
||||
};
|
||||
|
||||
static int __init btfm_swr_init(void)
|
||||
{
|
||||
BTFMSWR_INFO("");
|
||||
return swr_driver_register(&btfm_swr_driver);
|
||||
}
|
||||
|
||||
static void __exit btfm_swr_exit(void)
|
||||
{
|
||||
BTFMSWR_INFO("");
|
||||
swr_driver_unregister(&btfm_swr_driver);
|
||||
}
|
||||
|
||||
module_init(btfm_swr_init);
|
||||
module_exit(btfm_swr_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("BTFM SoundWire Slave driver");
|
||||
|
103
soundwire/btfm_swr.h
Normal file
103
soundwire/btfm_swr.h
Normal file
@@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef BTFM_SWR_H
|
||||
#define BTFM_SWR_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <bindings/audio-codec-port-types.h>
|
||||
#include "soc/soundwire.h"
|
||||
|
||||
#define SWR_SLAVE_COMPATIBLE_STR "btfmswr_slave"
|
||||
|
||||
|
||||
#define BTFMSWR_DBG(fmt, arg...) pr_debug("%s: " fmt "\n", __func__, ## arg)
|
||||
#define BTFMSWR_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg)
|
||||
#define BTFMSWR_ERR(fmt, arg...) pr_err("%s: " fmt "\n", __func__, ## arg)
|
||||
|
||||
extern struct btfmswr *pbtfmswr;
|
||||
|
||||
// assumption is that we use adjacent channels
|
||||
#define ONE_CHANNEL_MASK 1
|
||||
#define TWO_CHANNEL_MASK 3
|
||||
|
||||
#define MAX_BT_PORTS 1
|
||||
|
||||
/* Codec driver defines */
|
||||
enum {
|
||||
FMAUDIO_TX = 0,
|
||||
BTAUDIO_TX,
|
||||
BTAUDIO_RX,
|
||||
BTAUDIO_A2DP_SINK_TX,
|
||||
BTFM_NUM_CODEC_DAIS
|
||||
};
|
||||
|
||||
enum {
|
||||
EVROS = 0,
|
||||
GANGES = 1,
|
||||
MAX_SOC_ID = 0xFF
|
||||
};
|
||||
|
||||
|
||||
struct btfmswr_dai_port_info {
|
||||
int dai_id;
|
||||
char *dai_name;
|
||||
uint8_t port;
|
||||
};
|
||||
|
||||
struct soc_port_mapping {
|
||||
// enumeration address of BT SOC
|
||||
u64 ea;
|
||||
struct btfmswr_dai_port_info port_info[BTFM_NUM_CODEC_DAIS];
|
||||
};
|
||||
|
||||
|
||||
struct btfmswr {
|
||||
struct device *dev;
|
||||
struct swr_device *swr_slave;
|
||||
bool initialized;
|
||||
uint32_t sample_rate;
|
||||
uint32_t bps;
|
||||
uint16_t direction;
|
||||
uint8_t num_channels;
|
||||
int soc_index;
|
||||
struct soc_port_mapping *p_dai_port;
|
||||
};
|
||||
|
||||
/**
|
||||
* btfm_swr_hw_init: Initialize soundwire slave device
|
||||
* Returns:
|
||||
* 0: Success
|
||||
* else: Fail
|
||||
*/
|
||||
int btfm_swr_hw_init(void);
|
||||
|
||||
int btfm_get_bt_soc_index(int chipset_ver);
|
||||
|
||||
int btfm_swr_enable_port(u8 port_num, u8 ch_count, u32 sample_rate,
|
||||
u8 port_type);
|
||||
|
||||
|
||||
int btfm_swr_disable_port(u8 port_num, u8 ch_count, u8 usecase);
|
||||
|
||||
/**
|
||||
* btfm_swr_register_codec: Register codec driver with ALSA
|
||||
* @btfmswr: swr slave device data pointer.
|
||||
* Returns:
|
||||
* -ENOMEM
|
||||
* 0
|
||||
*/
|
||||
int btfm_swr_register_codec(struct btfmswr *btfmswr);
|
||||
|
||||
/**
|
||||
* btfm_swr_unregister_codec: Unregister codec driver with ALSA
|
||||
* @dev: device node
|
||||
* Returns:
|
||||
* VOID
|
||||
*/
|
||||
void btfm_swr_unregister_codec(struct device *dev);
|
||||
#endif /* BTFM_SWR_H */
|
341
soundwire/btfm_swr_codec.c
Normal file
341
soundwire/btfm_swr_codec.c
Normal file
@@ -0,0 +1,341 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include "btfm_swr.h"
|
||||
|
||||
static int bt_soc_enable_status;
|
||||
int btfm_feedback_ch_setting;
|
||||
|
||||
static int btfm_swr_codec_write(struct snd_soc_component *codec,
|
||||
unsigned int reg, unsigned int value)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int btfm_swr_codec_read(struct snd_soc_component *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
ucontrol->value.integer.value[0] = bt_soc_enable_status;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int btfm_soc_status_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
ucontrol->value.integer.value[0] = btfm_feedback_ch_setting;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int btfm_put_feedback_ch_setting(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
btfm_feedback_ch_setting = ucontrol->value.integer.value[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct snd_kcontrol_new status_controls[] = {
|
||||
SOC_SINGLE_EXT("BT SOC status", 0, 0, 1, 0,
|
||||
btfm_soc_status_get,
|
||||
btfm_soc_status_put),
|
||||
SOC_SINGLE_EXT("BT set feedback channel", 0, 0, 1, 0,
|
||||
btfm_get_feedback_ch_setting,
|
||||
btfm_put_feedback_ch_setting)
|
||||
};
|
||||
|
||||
|
||||
static int btfm_swr_codec_probe(struct snd_soc_component *codec)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
snd_soc_add_component_controls(codec, status_controls,
|
||||
ARRAY_SIZE(status_controls));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void btfm_swr_codec_remove(struct snd_soc_component *codec)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
}
|
||||
|
||||
static int btfm_swr_dai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
BTFMSWR_INFO("substream = %s stream = %d dai->name = %s",
|
||||
substream->name, substream->stream, dai->name);
|
||||
ret = btfm_swr_hw_init();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void btfm_swr_dai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int ret = 0;
|
||||
u8 port_type;
|
||||
|
||||
BTFMSWR_INFO("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
|
||||
dai->id, dai->rate);
|
||||
|
||||
switch (dai->id) {
|
||||
case FMAUDIO_TX:
|
||||
port_type = FM_AUDIO_TX1;
|
||||
break;
|
||||
case BTAUDIO_TX:
|
||||
port_type = BT_AUDIO_TX1;
|
||||
break;
|
||||
case BTAUDIO_RX:
|
||||
port_type = BT_AUDIO_RX1;
|
||||
break;
|
||||
case BTAUDIO_A2DP_SINK_TX:
|
||||
port_type = BT_AUDIO_TX2;
|
||||
break;
|
||||
case BTFM_NUM_CODEC_DAIS:
|
||||
default:
|
||||
BTFMSWR_ERR("dai->id is invalid:%d", dai->id);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = btfm_swr_disable_port(pbtfmswr->p_dai_port->port_info[dai->id].port,
|
||||
pbtfmswr->num_channels, port_type);
|
||||
|
||||
}
|
||||
|
||||
static int btfm_swr_dai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
pbtfmswr->bps = params_width(params);
|
||||
pbtfmswr->direction = substream->stream;
|
||||
pbtfmswr->num_channels = params_channels(params);
|
||||
|
||||
BTFMSWR_INFO("dai->name = %s dai id %x rate %d bps %d num_ch %d",
|
||||
dai->name, dai->id, params_rate(params), params_width(params),
|
||||
params_channels(params));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btfm_swr_dai_prepare(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
u8 port_type;
|
||||
|
||||
bt_soc_enable_status = 0;
|
||||
BTFMSWR_INFO("dai->name: %s, dai->id: %d, dai->rate: %d direction: %d", dai->name,
|
||||
dai->id, dai->rate, pbtfmswr->direction);
|
||||
|
||||
/* save sample rate */
|
||||
pbtfmswr->sample_rate = dai->rate;
|
||||
|
||||
switch (dai->id) {
|
||||
case FMAUDIO_TX:
|
||||
port_type = FM_AUDIO_TX1;
|
||||
break;
|
||||
case BTAUDIO_TX:
|
||||
port_type = BT_AUDIO_TX1;
|
||||
break;
|
||||
case BTAUDIO_RX:
|
||||
port_type = BT_AUDIO_RX1;
|
||||
break;
|
||||
case BTAUDIO_A2DP_SINK_TX:
|
||||
port_type = BT_AUDIO_TX2;
|
||||
break;
|
||||
case BTFM_NUM_CODEC_DAIS:
|
||||
default:
|
||||
BTFMSWR_ERR("dai->id is invalid:%d", dai->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = btfm_swr_enable_port(pbtfmswr->p_dai_port->port_info[dai->id].port,
|
||||
pbtfmswr->num_channels, dai->rate, port_type);
|
||||
|
||||
/* save the enable channel status */
|
||||
if (ret == 0)
|
||||
bt_soc_enable_status = 1;
|
||||
|
||||
if (ret == -EISCONN) {
|
||||
BTFMSWR_ERR("channel opened without closing, returning success");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* This function will be called once during boot up */
|
||||
static int btfm_swr_dai_set_channel_map(struct snd_soc_dai *dai,
|
||||
unsigned int tx_num, unsigned int *tx_slot,
|
||||
unsigned int rx_num, unsigned int *rx_slot)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btfm_swr_dai_get_channel_map(struct snd_soc_dai *dai,
|
||||
unsigned int *tx_num, unsigned int *tx_slot,
|
||||
unsigned int *rx_num, unsigned int *rx_slot)
|
||||
{
|
||||
*rx_slot = 0;
|
||||
*tx_slot = 0;
|
||||
*rx_num = 0;
|
||||
*tx_num = 0;
|
||||
|
||||
switch (dai->id) {
|
||||
case FMAUDIO_TX:
|
||||
case BTAUDIO_TX:
|
||||
case BTAUDIO_A2DP_SINK_TX:
|
||||
*tx_num = pbtfmswr->num_channels;
|
||||
*tx_slot = pbtfmswr->num_channels == 2 ? TWO_CHANNEL_MASK :
|
||||
ONE_CHANNEL_MASK;
|
||||
break;
|
||||
case BTAUDIO_RX:
|
||||
*rx_num = pbtfmswr->num_channels;
|
||||
*rx_slot = pbtfmswr->num_channels == 2 ? TWO_CHANNEL_MASK :
|
||||
ONE_CHANNEL_MASK;
|
||||
break;
|
||||
|
||||
default:
|
||||
BTFMSWR_ERR("Unsupported DAI %d", dai->id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static struct snd_soc_dai_ops btfmswr_dai_ops = {
|
||||
.startup = btfm_swr_dai_startup,
|
||||
.shutdown = btfm_swr_dai_shutdown,
|
||||
.hw_params = btfm_swr_dai_hw_params,
|
||||
.prepare = btfm_swr_dai_prepare,
|
||||
.set_channel_map = btfm_swr_dai_set_channel_map,
|
||||
.get_channel_map = btfm_swr_dai_get_channel_map,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver btfmswr_dai[] = {
|
||||
{ /* FM Audio data multiple channel : FM -> lpass */
|
||||
.name = "btfm_fm_swr_tx",
|
||||
.id = FMAUDIO_TX,
|
||||
.capture = {
|
||||
.stream_name = "FM TX Capture",
|
||||
.rates = SNDRV_PCM_RATE_48000, /* 48 KHz */
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
|
||||
.rate_max = 48000,
|
||||
.rate_min = 48000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
},
|
||||
.ops = &btfmswr_dai_ops,
|
||||
},
|
||||
{ /* Bluetooth SCO voice uplink: bt -> lpass */
|
||||
.name = "btfm_bt_sco_swr_tx",
|
||||
.id = BTAUDIO_TX,
|
||||
.capture = {
|
||||
.stream_name = "SCO TX Capture",
|
||||
/* 8/16/44.1/48/88.2/96/192 Khz */
|
||||
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
|
||||
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
|
||||
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
|
||||
| SNDRV_PCM_RATE_192000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
|
||||
.rate_max = 192000,
|
||||
.rate_min = 8000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
},
|
||||
.ops = &btfmswr_dai_ops,
|
||||
},
|
||||
{ /* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
|
||||
.name = "btfm_bt_sco_a2dp_swr_rx",
|
||||
.id = BTAUDIO_RX,
|
||||
.playback = {
|
||||
.stream_name = "SCO A2DP RX Playback",
|
||||
/* 8/16/44.1/48/88.2/96/192 Khz */
|
||||
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
|
||||
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
|
||||
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
|
||||
| SNDRV_PCM_RATE_192000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
|
||||
.rate_max = 192000,
|
||||
.rate_min = 8000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
},
|
||||
.ops = &btfmswr_dai_ops,
|
||||
},
|
||||
{ /* Bluetooth A2DP sink: bt -> lpass */
|
||||
.name = "btfm_a2dp_sink_swr_tx",
|
||||
.id = BTAUDIO_A2DP_SINK_TX,
|
||||
.capture = {
|
||||
.stream_name = "A2DP sink TX Capture",
|
||||
/* 8/16/44.1/48/88.2/96/192 Khz */
|
||||
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
|
||||
| SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000
|
||||
| SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000
|
||||
| SNDRV_PCM_RATE_192000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 bits */
|
||||
.rate_max = 192000,
|
||||
.rate_min = 8000,
|
||||
.channels_min = 1,
|
||||
.channels_max = 1,
|
||||
},
|
||||
.ops = &btfmswr_dai_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver btfmswr_codec = {
|
||||
.probe = btfm_swr_codec_probe,
|
||||
.remove = btfm_swr_codec_remove,
|
||||
.read = btfm_swr_codec_read,
|
||||
.write = btfm_swr_codec_write,
|
||||
};
|
||||
|
||||
int btfm_swr_register_codec(struct btfmswr *btfm_swr)
|
||||
{
|
||||
int ret = 0;
|
||||
struct device *dev = btfm_swr->dev;
|
||||
|
||||
BTFMSWR_DBG("");
|
||||
dev_err(dev, "\n");
|
||||
|
||||
/* Register Codec driver */
|
||||
ret = snd_soc_register_component(dev, &btfmswr_codec,
|
||||
btfmswr_dai, ARRAY_SIZE(btfmswr_dai));
|
||||
if (ret)
|
||||
BTFMSWR_ERR("failed to register codec (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void btfm_swr_unregister_codec(struct device *dev)
|
||||
{
|
||||
BTFMSWR_DBG("");
|
||||
/* Unregister Codec driver */
|
||||
snd_soc_unregister_component(dev);
|
||||
}
|
||||
|
||||
|
||||
MODULE_DESCRIPTION("BTFM SoundWire Codec driver");
|
||||
MODULE_LICENSE("GPL v2");
|
44
soundwire/btfm_swr_slave.c
Normal file
44
soundwire/btfm_swr_slave.c
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "btfm_swr.h"
|
||||
#include "btfm_swr_slave.h"
|
||||
|
||||
struct soc_port_mapping slave_port[] = {
|
||||
// Evros
|
||||
{
|
||||
.ea = EVROS_EA,
|
||||
.port_info[0].dai_id = FMAUDIO_TX,
|
||||
.port_info[0].port = 5,
|
||||
|
||||
.port_info[1].dai_id = BTAUDIO_TX,
|
||||
.port_info[1].port = 3,
|
||||
|
||||
.port_info[2].dai_id = BTAUDIO_RX,
|
||||
.port_info[2].port = 1,
|
||||
|
||||
.port_info[3].dai_id = BTAUDIO_A2DP_SINK_TX,
|
||||
.port_info[3].port = 4,
|
||||
},
|
||||
|
||||
// Ganges
|
||||
{
|
||||
.ea = GANGES_EA,
|
||||
// FM is not supported on Ganges. populate with invalid port number
|
||||
.port_info[0].dai_id = FMAUDIO_TX,
|
||||
.port_info[0].port = BTFM_INVALID_PORT,
|
||||
|
||||
.port_info[1].dai_id = BTAUDIO_TX,
|
||||
.port_info[1].port = 4,
|
||||
|
||||
.port_info[2].dai_id = BTAUDIO_RX,
|
||||
.port_info[2].port = 1,
|
||||
|
||||
.port_info[3].dai_id = BTAUDIO_A2DP_SINK_TX,
|
||||
.port_info[3].port = 5,
|
||||
},
|
||||
};
|
||||
|
45
soundwire/btfm_swr_slave.h
Normal file
45
soundwire/btfm_swr_slave.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef BTFM_SWR_SLAVE_H
|
||||
#define BTFM_SWR_SLAVE_H
|
||||
|
||||
#include "btfm_swr.h"
|
||||
|
||||
/* Registers Address */
|
||||
|
||||
/* Register Bit Setting */
|
||||
#define SLAVE_ENABLE_OVERRUN_AUTO_RECOVERY (0x1 << 1)
|
||||
#define SLAVE_ENABLE_UNDERRUN_AUTO_RECOVERY (0x1 << 0)
|
||||
#define SLAVE_SB_PGD_PORT_ENABLE (0x1 << 0)
|
||||
#define SLAVE_SB_PGD_PORT_DISABLE (0x0 << 0)
|
||||
|
||||
|
||||
#define BTFM_INVALID_PORT 0xFF
|
||||
|
||||
extern struct soc_port_mapping slave_port[];
|
||||
|
||||
enum {
|
||||
QCA_GANGES_SOC_ID_0100 = 0x40210100,
|
||||
QCA_GANGES_SOC_ID_0200 = 0x40210200,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
QCA_EVROS_SOC_ID_0100 = 0x40200100,
|
||||
QCA_EVROS_SOC_ID_0200 = 0x40200200,
|
||||
};
|
||||
|
||||
|
||||
enum {
|
||||
EVROS_EA = 0x0108170220,
|
||||
GANGES_EA = 0x0208170220,
|
||||
};
|
||||
|
||||
/* Specific defines for slave slimbus device */
|
||||
#define SLAVE_SWR_REG_OFFSET 0x0800
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user