sm8750: init kernel modules repo

This commit is contained in:
2025-08-11 12:21:01 +02:00
parent 2681143b87
commit facad83b01
8851 changed files with 6894561 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0-only
config BTFM_SLIM
tristate "MSM Bluetooth/FM Slimbus Device"
depends on MSM_BT_POWER
help
This enables BT/FM slimbus driver to get multiple audio channel.
This will make use of slimbus platform driver and slimbus
codec driver to communicate with slimbus machine driver and LPSS which
is Slimbus master.Slimbus slave initialization and configuration
will be done through this driver.
Say Y here to compile support for Bluetooth slimbus driver
into the kernel or say M to compile as a module.
config SLIM_BTFM_CODEC
tristate "MSM Bluetooth/FM Slimbus Device using BTFM codec driver"
depends on MSM_BT_POWER
depends on BTFM_CODEC
help
This enables BT/FM slimbus driver to use btfm codec driver as
interface to interacts with codec driver.
Say Y here to compile support for Bluetooth slimbus driver
into the kernel or say M to compile as a module.

View File

@@ -0,0 +1,8 @@
ccflags-y += -I$(BT_ROOT)/include
ccflags-y += -I$(BT_ROOT)/btfmcodec/include
#Below src is for BTFM SLAVE CODEC Driver support on LE platform.
bt_fm_slim-objs := btfm_slim.o btfm_slim_codec.o btfm_slim_slave.o
obj-$(CONFIG_BTFM_SLIM) += bt_fm_slim.o
# Below src is for BTFM Driver support based on btfm codec
btfm_slim_codec-objs := btfm_slim.o btfm_slim_hw_interface.o btfm_slim_slave.o
obj-$(CONFIG_SLIM_BTFM_CODEC) += btfm_slim_codec.o

View File

@@ -0,0 +1,750 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021-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_slim.h"
#include "btfm_slim_slave.h"
#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC)
#include "btfm_slim_hw_interface.h"
#endif
#define DELAY_FOR_PORT_OPEN_MS (200)
#define SLIM_MANF_ID_QCOM 0x217
#define SLIM_PROD_CODE 0x221
#define BT_CMD_SLIM_TEST 0xbfac
struct class *btfm_slim_class;
static int btfm_slim_major;
struct btfmslim *btfm_slim_drv_data;
static int btfm_num_ports_open;
static bool is_registered;
int btfm_slim_write(struct btfmslim *btfmslim,
uint16_t reg, uint8_t reg_val, uint8_t pgd)
{
int ret = -1;
uint32_t reg_addr;
int slim_write_tries = SLIM_SLAVE_RW_MAX_TRIES;
BTFMSLIM_INFO("Write to %s", pgd?"PGD":"IFD");
reg_addr = SLIM_SLAVE_REG_OFFSET + reg;
for ( ; slim_write_tries != 0; slim_write_tries--) {
mutex_lock(&btfmslim->xfer_lock);
ret = slim_writeb(pgd ? btfmslim->slim_pgd :
&btfmslim->slim_ifd, reg_addr, reg_val);
mutex_unlock(&btfmslim->xfer_lock);
if (ret) {
BTFMSLIM_DBG("retrying to Write 0x%02x to reg 0x%x ret %d",
reg_val, reg_addr, ret);
} else {
BTFMSLIM_DBG("Written 0x%02x to reg 0x%x ret %d", reg_val, reg_addr, ret);
break;
}
usleep_range(5000, 5100);
}
if (ret) {
BTFMSLIM_DBG("retrying to Write 0x%02x to reg 0x%x ret %d",
reg_val, reg_addr, ret);
}
return ret;
}
int btfm_slim_read(struct btfmslim *btfmslim, uint32_t reg, uint8_t pgd)
{
int ret = -1;
int slim_read_tries = SLIM_SLAVE_RW_MAX_TRIES;
uint32_t reg_addr;
BTFMSLIM_DBG("Read from %s", pgd?"PGD":"IFD");
reg_addr = SLIM_SLAVE_REG_OFFSET + reg;
for ( ; slim_read_tries != 0; slim_read_tries--) {
mutex_lock(&btfmslim->xfer_lock);
ret = slim_readb(pgd ? btfmslim->slim_pgd :
&btfmslim->slim_ifd, reg_addr);
BTFMSLIM_DBG("Read 0x%02x from reg 0x%x", ret, reg_addr);
mutex_unlock(&btfmslim->xfer_lock);
if (ret > 0)
break;
usleep_range(5000, 5100);
}
return ret;
}
int btfm_slim_enable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
uint8_t rxport, uint32_t rates, uint8_t nchan)
{
int ret = -1;
int i = 0;
struct btfmslim_ch *chan = ch;
int chipset_ver;
if (!btfmslim || !ch)
return -EINVAL;
BTFMSLIM_DBG("port: %d ch: %d", ch->port, ch->ch);
chan->dai.sruntime = slim_stream_allocate(btfmslim->slim_pgd, "BTFM_SLIM");
if (chan->dai.sruntime == NULL) {
BTFMSLIM_ERR("slim_stream_allocate failed");
return -EINVAL;
}
chan->dai.sconfig.bps = btfmslim->bps;
chan->dai.sconfig.direction = btfmslim->direction;
chan->dai.sconfig.rate = rates;
chan->dai.sconfig.ch_count = nchan;
chan->dai.sconfig.chs = kcalloc(nchan, sizeof(unsigned int), GFP_KERNEL);
if (!chan->dai.sconfig.chs)
return -ENOMEM;
for (i = 0; i < nchan; i++, ch++) {
/* Enable port through registration setting */
if (btfmslim->vendor_port_en) {
ret = btfmslim->vendor_port_en(btfmslim, ch->port,
rxport, 1);
if (ret < 0) {
BTFMSLIM_ERR("vendor_port_en failed ret[%d]",
ret);
goto error;
}
}
chan->dai.sconfig.chs[i] = ch->ch;
chan->dai.sconfig.port_mask |= BIT(ch->port);
}
/* Activate the channel immediately */
BTFMSLIM_INFO("port: %d, ch: %d", chan->port, chan->ch);
chipset_ver = btpower_get_chipset_version();
BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
/* for feedback channel, PCM bit should not be set */
if (btfm_feedback_ch_setting) {
BTFMSLIM_DBG("port open for feedback ch, not setting PCM bit");
//prop.dataf = SLIM_CH_DATAF_NOT_DEFINED;
/* reset so that next port open sets the data format properly */
btfm_feedback_ch_setting = 0;
}
ret = slim_stream_prepare(chan->dai.sruntime, &chan->dai.sconfig);
if (ret) {
BTFMSLIM_ERR("slim_stream_prepare failed = %d", ret);
goto error;
}
ret = slim_stream_enable(chan->dai.sruntime);
if (ret) {
BTFMSLIM_ERR("slim_stream_enable failed = %d", ret);
goto error;
}
if (ret == 0)
btfm_num_ports_open++;
BTFMSLIM_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
return ret;
error:
BTFMSLIM_INFO("error %d while opening port, btfm_num_ports_open: %d",
ret, btfm_num_ports_open);
kfree(chan->dai.sconfig.chs);
chan->dai.sconfig.chs = NULL;
return ret;
}
int btfm_slim_disable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
uint8_t rxport, uint8_t nchan)
{
int ret = -1;
int i = 0;
int chipset_ver = 0;
if (!btfmslim || !ch)
return -EINVAL;
BTFMSLIM_INFO("port:%d ", ch->port);
if (ch->dai.sruntime == NULL) {
BTFMSLIM_ERR("Channel not enabled yet. returning");
return -EINVAL;
}
if (rxport && (btfmslim->sample_rate == 44100 ||
btfmslim->sample_rate == 88200)) {
BTFMSLIM_INFO("disconnecting the ports, removing the channel");
/* disconnect the ports of the stream */
ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime,
true, false);
if (ret != 0)
BTFMSLIM_ERR("slim_stream_unprepare failed %d", ret);
}
ret = slim_stream_disable(ch->dai.sruntime);
if (ret != 0) {
BTFMSLIM_ERR("slim_stream_disable failed returned val = %d", ret);
if ((btfmslim->sample_rate != 44100) && (btfmslim->sample_rate != 88200)) {
/* disconnect the ports of the stream */
ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime,
true, false);
if (ret != 0)
BTFMSLIM_ERR("slim_stream_unprepare failed %d", ret);
}
}
/* free the ports allocated to the stream */
ret = slim_stream_unprepare_disconnect_port(ch->dai.sruntime, false, true);
if (ret != 0)
BTFMSLIM_ERR("slim_stream_unprepare failed returned val = %d", ret);
/* Disable port through registration setting */
for (i = 0; i < nchan; i++, ch++) {
if (btfmslim->vendor_port_en) {
ret = btfmslim->vendor_port_en(btfmslim, ch->port,
rxport, 0);
if (ret < 0) {
BTFMSLIM_ERR("vendor_port_en failed [%d]", ret);
break;
}
}
}
ch->dai.sconfig.port_mask = 0;
if (ch->dai.sconfig.chs != NULL) {
kfree(ch->dai.sconfig.chs);
BTFMSLIM_INFO("setting ch->dai.sconfig.chs to NULL");
ch->dai.sconfig.chs = NULL;
} else
BTFMSLIM_ERR("ch->dai.sconfig.chs is already NULL");
if (btfm_num_ports_open > 0)
btfm_num_ports_open--;
ch->dai.sruntime = NULL;
BTFMSLIM_INFO("btfm_num_ports_open: %d", btfm_num_ports_open);
chipset_ver = btpower_get_chipset_version();
if (btfm_num_ports_open == 0 && (chipset_ver == QCA_HSP_SOC_ID_0200 ||
chipset_ver == QCA_HSP_SOC_ID_0210 ||
chipset_ver == QCA_HSP_SOC_ID_1201 ||
chipset_ver == QCA_HSP_SOC_ID_1211 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0100 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0101 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0200 ||
chipset_ver == QCA_APACHE_SOC_ID_0100 ||
chipset_ver == QCA_APACHE_SOC_ID_0110 ||
chipset_ver == QCA_APACHE_SOC_ID_0121 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0100 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0110 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0120)) {
BTFMSLIM_INFO("SB reset needed after all ports disabled, sleeping");
msleep(DELAY_FOR_PORT_OPEN_MS);
}
return ret;
}
static int btfm_slim_alloc_port(struct btfmslim *btfmslim)
{
int ret = -EINVAL, i;
int chipset_ver;
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
if (!btfmslim)
return ret;
chipset_ver = btpower_get_chipset_version();
BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
rx_chs = btfmslim->rx_chs;
tx_chs = btfmslim->tx_chs;
if ((chipset_ver >= QCA_CHEROKEE_SOC_ID_0310) &&
(chipset_ver <= QCA_CHEROKEE_SOC_ID_0320_UMC)) {
for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, tx_chs++) {
if (tx_chs->port == SLAVE_SB_PGD_PORT_TX1_FM)
tx_chs->port = CHRKVER3_SB_PGD_PORT_TX1_FM;
else if (tx_chs->port == SLAVE_SB_PGD_PORT_TX2_FM)
tx_chs->port = CHRKVER3_SB_PGD_PORT_TX2_FM;
BTFMSLIM_INFO("Tx port:%d", tx_chs->port);
}
tx_chs = btfmslim->tx_chs;
}
if (!rx_chs || !tx_chs)
return ret;
return 0;
}
static int btfm_slim_get_logical_addr(struct slim_device *slim)
{
int ret = 0;
const unsigned long timeout = jiffies +
msecs_to_jiffies(SLIM_SLAVE_PRESENT_TIMEOUT);
BTFMSLIM_INFO("");
do {
ret = slim_get_logical_addr(slim);
if (!ret) {
BTFMSLIM_DBG("Assigned l-addr: 0x%x", slim->laddr);
break;
}
/* Give SLIMBUS time to report present and be ready. */
usleep_range(1000, 1100);
BTFMSLIM_DBG("retyring get logical addr");
} while (time_before(jiffies, timeout));
return ret;
}
int btfm_slim_hw_init(struct btfmslim *btfmslim)
{
int ret = -1;
int chipset_ver;
struct slim_device *slim;
struct slim_device *slim_ifd;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
if (btfmslim->enabled) {
BTFMSLIM_DBG("Already enabled");
return 0;
}
slim = btfmslim->slim_pgd;
slim_ifd = &btfmslim->slim_ifd;
mutex_lock(&btfmslim->io_lock);
BTFMSLIM_INFO(
"PGD Enum Addr: mfr id:%.02x prod code:%.02x dev ind:%.02x ins:%.02x",
slim->e_addr.manf_id, slim->e_addr.prod_code,
slim->e_addr.dev_index, slim->e_addr.instance);
chipset_ver = btpower_get_chipset_version();
BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);
if (chipset_ver == QCA_HSP_SOC_ID_0100 ||
chipset_ver == QCA_HSP_SOC_ID_0110 ||
chipset_ver == QCA_HSP_SOC_ID_0200 ||
chipset_ver == QCA_HSP_SOC_ID_0210 ||
chipset_ver == QCA_HSP_SOC_ID_1201 ||
chipset_ver == QCA_HSP_SOC_ID_1211) {
BTFMSLIM_INFO("chipset is hastings prime, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = SLIM_PROD_CODE;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = SLIM_PROD_CODE;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
} else if (chipset_ver == QCA_MOSELLE_SOC_ID_0100 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0110 ||
chipset_ver == QCA_MOSELLE_SOC_ID_0120) {
BTFMSLIM_INFO("chipset is Moselle, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = 0x222;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = 0x222;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
} else if (chipset_ver == QCA_HAMILTON_SOC_ID_0100 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0101 ||
chipset_ver == QCA_HAMILTON_SOC_ID_0200) {
BTFMSLIM_INFO("chipset is Hamliton, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = 0x220;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = 0x220;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
} else if (chipset_ver == QCA_CHEROKEE_SOC_ID_0200 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0201 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0210 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0211 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0310 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0320 ||
chipset_ver == QCA_CHEROKEE_SOC_ID_0320_UMC ||
chipset_ver == QCA_APACHE_SOC_ID_0100 ||
chipset_ver == QCA_APACHE_SOC_ID_0110 ||
chipset_ver == QCA_APACHE_SOC_ID_0120 ||
chipset_ver == QCA_APACHE_SOC_ID_0121 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0101 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0110 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0120 ||
chipset_ver == QCA_COMANCHE_SOC_ID_0130 ||
chipset_ver == QCA_COMANCHE_SOC_ID_4130 ||
chipset_ver == QCA_COMANCHE_SOC_ID_5120 ||
chipset_ver == QCA_COMANCHE_SOC_ID_5130 ) {
BTFMSLIM_INFO("chipset is Chk/Apache/CMC, overwriting EA");
slim->is_laddr_valid = false;
slim->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim->e_addr.prod_code = 0x220;
slim->e_addr.dev_index = 0x01;
slim->e_addr.instance = 0x0;
/* we are doing this to indicate that this is not a child node
* (doesn't have call back functions). Needed only for querying
* logical address.
*/
slim_ifd->dev.driver = NULL;
slim_ifd->ctrl = btfmslim->slim_pgd->ctrl; //slimbus controller structure.
slim_ifd->is_laddr_valid = false;
slim_ifd->e_addr.manf_id = SLIM_MANF_ID_QCOM;
slim_ifd->e_addr.prod_code = 0x220;
slim_ifd->e_addr.dev_index = 0x0;
slim_ifd->e_addr.instance = 0x0;
slim_ifd->laddr = 0x0;
}
BTFMSLIM_INFO(
"PGD Enum Addr: manu id:%.02x prod code:%.02x dev idx:%.02x instance:%.02x",
slim->e_addr.manf_id, slim->e_addr.prod_code,
slim->e_addr.dev_index, slim->e_addr.instance);
BTFMSLIM_INFO(
"IFD Enum Addr: manu id:%.02x prod code:%.02x dev idx:%.02x instance:%.02x",
slim_ifd->e_addr.manf_id, slim_ifd->e_addr.prod_code,
slim_ifd->e_addr.dev_index, slim_ifd->e_addr.instance);
/* Assign Logical Address for PGD (Ported Generic Device)
* enumeration address
*/
ret = btfm_slim_get_logical_addr(btfmslim->slim_pgd);
if (ret) {
BTFMSLIM_ERR("failed to get slimbus logical address: %d", ret);
goto error;
}
/* Assign Logical Address for Ported Generic Device
* enumeration address
*/
ret = btfm_slim_get_logical_addr(&btfmslim->slim_ifd);
if (ret) {
BTFMSLIM_ERR("failed to get slimbus logical address: %d", ret);
goto error;
}
ret = btfm_slim_alloc_port(btfmslim);
if (ret != 0)
goto error;
/* Start vendor specific initialization and get port information */
if (btfmslim->vendor_init)
ret = btfmslim->vendor_init(btfmslim);
/* Only when all registers read/write successfully, it set to
* enabled status
*/
btfmslim->enabled = 1;
error:
mutex_unlock(&btfmslim->io_lock);
return ret;
}
int btfm_slim_hw_deinit(struct btfmslim *btfmslim)
{
int ret = 0;
BTFMSLIM_INFO("");
if (!btfmslim)
return -EINVAL;
if (!btfmslim->enabled) {
BTFMSLIM_DBG("Already disabled");
return 0;
}
mutex_lock(&btfmslim->io_lock);
btfmslim->enabled = 0;
mutex_unlock(&btfmslim->io_lock);
return ret;
}
#if IS_ENABLED (CONFIG_BTFM_SLIM)
void btfm_slim_get_hwep_details(struct slim_device *dev, struct btfmslim *btfm_slim)
{
}
#else
void btfm_slim_get_hwep_details(struct slim_device *slim, struct btfmslim *btfm_slim)
{
struct device_node *np = slim->dev.of_node;
const __be32 *prop;
struct btfmslim_ch *rx_chs = btfm_slim->rx_chs;
struct btfmslim_ch *tx_chs = btfm_slim->tx_chs;
int len;
prop = of_get_property(np, "qcom,btslim-address", &len);
if (prop) {
btfm_slim->device_id = be32_to_cpup(&prop[0]);
BTFMSLIM_DBG("hwep slim address define in dt %08x", btfm_slim->device_id);
} else {
BTFMSLIM_ERR("btslim-address is not defined in dt using default address");
btfm_slim->device_id = 0;
}
if (!rx_chs || !tx_chs) {
BTFMSLIM_ERR("either rx/tx channels are configured to null");
return;
}
prop = of_get_property(np, "qcom,btslimrx-channels", &len);
if (prop) {
/* Check if we need any protection for index */
rx_chs[0].ch = (uint8_t)be32_to_cpup(&prop[0]);
rx_chs[1].ch = (uint8_t)be32_to_cpup(&prop[1]);
BTFMSLIM_DBG("Rx: id\tname\tport\tch");
BTFMSLIM_DBG(" %d\t%s\t%d\t%d", rx_chs[0].id,
rx_chs[0].name, rx_chs[0].port,
rx_chs[0].ch);
BTFMSLIM_DBG(" %d\t%s\t%d\t%d", rx_chs[1].id,
rx_chs[1].name, rx_chs[1].port,
rx_chs[1].ch);
} else {
BTFMSLIM_ERR("btslimrx channels are missing in dt using default values");
}
prop = of_get_property(np, "qcom,btslimtx-channels", &len);
if (prop) {
/* Check if we need any protection for index */
tx_chs[0].ch = (uint8_t)be32_to_cpup(&prop[0]);
tx_chs[1].ch = (uint8_t)be32_to_cpup(&prop[1]);
BTFMSLIM_DBG("Tx: id\tname\tport\tch");
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", tx_chs[0].id,
tx_chs[0].name, tx_chs[0].port,
tx_chs[0].ch);
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", tx_chs[1].id,
tx_chs[1].name, tx_chs[1].port,
tx_chs[1].ch);
} else {
BTFMSLIM_ERR("btslimtx channels are missing in dt using default values");
}
}
#endif
static int btfm_slim_status(struct slim_device *sdev,
enum slim_device_status status)
{
int ret = 0;
struct device *dev = &sdev->dev;
struct btfmslim *btfm_slim;
btfm_slim = dev_get_drvdata(dev);
#if IS_ENABLED(CONFIG_BTFM_SLIM)
if (!is_registered) {
ret = btfm_slim_register_codec(btfm_slim);
}
#else
if (!is_registered) {
btfm_slim_get_hwep_details(sdev, btfm_slim);
ret = btfm_slim_register_hw_ep(btfm_slim);
}
#endif
if (!ret)
is_registered = true;
else
BTFMSLIM_ERR("error, registering slimbus codec failed");
return ret;
}
static long btfm_slim_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch (cmd) {
case BT_CMD_SLIM_TEST:
BTFMSLIM_INFO("cmd BT_CMD_SLIM_TEST, call btfm_slim_hw_init");
ret = btfm_slim_hw_init(btfm_slim_drv_data);
break;
}
return ret;
}
static const struct file_operations bt_dev_fops = {
.unlocked_ioctl = btfm_slim_ioctl,
.compat_ioctl = btfm_slim_ioctl,
};
static int btfm_slim_probe(struct slim_device *slim)
{
int ret = 0;
struct btfmslim *btfm_slim;
pr_info("%s: name = %s\n", __func__, dev_name(&slim->dev));
/*this as true during the probe then slimbus won't check for logical address*/
slim->is_laddr_valid = true;
is_registered = false;
dev_set_name(&slim->dev, "%s", BTFMSLIM_DEV_NAME);
pr_info("%s: name = %s\n", __func__, dev_name(&slim->dev));
BTFMSLIM_DBG("");
BTFMSLIM_ERR("is_laddr_valid is true");
if (!slim->ctrl)
return -EINVAL;
/* Allocation btfmslim data pointer */
btfm_slim = kzalloc(sizeof(struct btfmslim), GFP_KERNEL);
if (btfm_slim == NULL) {
BTFMSLIM_ERR("error, allocation failed");
return -ENOMEM;
}
/* BTFM Slimbus driver control data configuration */
btfm_slim->slim_pgd = slim;
/* Assign vendor specific function */
btfm_slim->rx_chs = SLIM_SLAVE_RXPORT;
btfm_slim->tx_chs = SLIM_SLAVE_TXPORT;
btfm_slim->vendor_init = SLIM_SLAVE_INIT;
btfm_slim->vendor_port_en = SLIM_SLAVE_PORT_EN;
/* Created Mutex for slimbus data transfer */
mutex_init(&btfm_slim->io_lock);
mutex_init(&btfm_slim->xfer_lock);
dev_set_drvdata(&slim->dev, btfm_slim);
/* Driver specific data allocation */
btfm_slim->dev = &slim->dev;
ret = btpower_register_slimdev(&slim->dev);
if (ret < 0) {
#if IS_ENABLED(CONFIG_BTFM_SLIM)
btfm_slim_unregister_codec(&slim->dev);
#else
btfm_slim_unregister_hwep();
#endif
ret = -EPROBE_DEFER;
goto dealloc;
}
btfm_slim_drv_data = btfm_slim;
btfm_slim_major = register_chrdev(0, "btfm_slim", &bt_dev_fops);
if (btfm_slim_major < 0) {
BTFMSLIM_ERR("%s: failed to allocate char dev\n", __func__);
ret = -1;
goto register_err;
}
btfm_slim_class = class_create("btfmslim-dev");
if (IS_ERR(btfm_slim_class)) {
BTFMSLIM_ERR("%s: coudn't create class\n", __func__);
ret = -1;
goto class_err;
}
if (device_create(btfm_slim_class, NULL, MKDEV(btfm_slim_major, 0),
NULL, "btfmslim") == NULL) {
BTFMSLIM_ERR("%s: failed to allocate char dev\n", __func__);
ret = -1;
goto device_err;
}
return ret;
device_err:
class_destroy(btfm_slim_class);
class_err:
unregister_chrdev(btfm_slim_major, "btfm_slim");
register_err:
#if IS_ENABLED(CONFIG_BTFM_SLIM)
btfm_slim_unregister_codec(&slim->dev);
#else
btfm_slim_unregister_hwep();
#endif
dealloc:
mutex_destroy(&btfm_slim->io_lock);
mutex_destroy(&btfm_slim->xfer_lock);
kfree(btfm_slim);
return ret;
}
static void btfm_slim_remove(struct slim_device *slim)
{
struct device *dev = &slim->dev;
struct btfmslim *btfm_slim = dev_get_drvdata(dev);
BTFMSLIM_DBG("");
mutex_destroy(&btfm_slim->io_lock);
mutex_destroy(&btfm_slim->xfer_lock);
snd_soc_unregister_component(&slim->dev);
kfree(btfm_slim);
}
static const struct slim_device_id btfm_slim_id[] = {
{
.manf_id = SLIM_MANF_ID_QCOM,
.prod_code = SLIM_PROD_CODE,
.dev_index = 0x1,
.instance = 0x0,
},
{
.manf_id = SLIM_MANF_ID_QCOM,
.prod_code = 0x220,
.dev_index = 0x1,
.instance = 0x0,
}
};
MODULE_DEVICE_TABLE(slim, btfm_slim_id);
static struct slim_driver btfm_slim_driver = {
.driver = {
.name = "btfmslim-driver",
.owner = THIS_MODULE,
},
.probe = btfm_slim_probe,
.device_status = btfm_slim_status,
.remove = btfm_slim_remove,
.id_table = btfm_slim_id
};
module_slim_driver(btfm_slim_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("BTFM Slimbus Slave driver");

View File

@@ -0,0 +1,176 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef BTFM_SLIM_H
#define BTFM_SLIM_H
#include <linux/slimbus.h>
#define BTFMSLIM_DBG(fmt, arg...) pr_debug("%s: " fmt "\n", __func__, ## arg)
#define BTFMSLIM_INFO(fmt, arg...) pr_info("%s: " fmt "\n", __func__, ## arg)
#define BTFMSLIM_ERR(fmt, arg...) pr_err("%s: " fmt "\n", __func__, ## arg)
/* Vendor specific defines
* This should redefines in slimbus slave specific header
*/
#define SLIM_SLAVE_COMPATIBLE_STR "btfmslim_slave"
#define SLIM_SLAVE_REG_OFFSET 0x0000
#define SLIM_SLAVE_RXPORT NULL
#define SLIM_SLAVE_TXPORT NULL
#define SLIM_SLAVE_INIT NULL
#define SLIM_SLAVE_PORT_EN NULL
/* Misc defines */
#define SLIM_SLAVE_RW_MAX_TRIES 3
#define SLIM_SLAVE_PRESENT_TIMEOUT 100
#define PGD 1
#define IFD 0
#if IS_ENABLED(CONFIG_BTFM_SLIM)
#define BTFMSLIM_DEV_NAME "btfmslim_slave"
#else
#define BTFMSLIM_DEV_NAME "btfmslim"
#endif
/* Codec driver defines */
enum {
BTFM_FM_SLIM_TX = 0,
BTFM_BT_SCO_SLIM_TX,
BTFM_BT_SCO_A2DP_SLIM_RX,
BTFM_BT_SPLIT_A2DP_SLIM_RX,
BTFM_SLIM_NUM_CODEC_DAIS
};
struct btfm_slim_codec_dai_data {
struct slim_stream_config sconfig;
struct slim_stream_runtime *sruntime;
};
struct btfmslim_ch {
int id;
char *name;
uint16_t port; /* slimbus port number */
uint8_t ch; /* slimbus channel number */
struct btfm_slim_codec_dai_data dai;
};
/* Slimbus Port defines - This should be redefined in specific device file */
#define BTFM_SLIM_PGD_PORT_LAST 0xFF
struct btfmslim {
struct device *dev;
struct slim_device *slim_pgd; //Physical address
struct slim_device slim_ifd; //Interface address
struct mutex io_lock;
struct mutex xfer_lock;
uint8_t enabled;
uint32_t num_rx_port;
uint32_t num_tx_port;
uint32_t sample_rate;
uint32_t bps;
uint16_t direction;
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
int (*vendor_init)(struct btfmslim *btfmslim);
int (*vendor_port_en)(struct btfmslim *btfmslim, uint8_t port_num,
uint8_t rxport, uint8_t enable);
#if IS_ENABLED(CONFIG_SLIM_BTFM_CODEC)
int device_id;
#endif
};
extern int btfm_feedback_ch_setting;
/**
* btfm_slim_hw_init: Initialize slimbus slave device
* Returns:
* 0: Success
* else: Fail
*/
int btfm_slim_hw_init(struct btfmslim *btfmslim);
/**
* btfm_slim_hw_deinit: Deinitialize slimbus slave device
* Returns:
* 0: Success
* else: Fail
*/
int btfm_slim_hw_deinit(struct btfmslim *btfmslim);
/**
* btfm_slim_write: write value to pgd or ifd device
* @btfmslim: slimbus slave device data pointer.
* @reg: slimbus slave register address
* @reg_val: value to write at register address
* @pgd: selection for device: either PGD or IFD
* Returns:
No of bytes written
-1
*/
int btfm_slim_write(struct btfmslim *btfmslim,
uint16_t reg, uint8_t reg_val, uint8_t pgd);
/**
* btfm_slim_read: read value from pgd or ifd device
* @btfmslim: slimbus slave device data pointer.
* @reg: slimbus slave register address
* @dest: data pointer to read
* @pgd: selection for device: either PGD or IFD
* Returns:
No of bytes read
-1
*/
int btfm_slim_read(struct btfmslim *btfmslim,
uint32_t reg, uint8_t pgd);
/**
* btfm_slim_enable_ch: enable channel for slimbus slave port
* @btfmslim: slimbus slave device data pointer.
* @ch: slimbus slave channel pointer
* @rxport: rxport or txport
* Returns:
* -EINVAL
* -ETIMEDOUT
* -ENOMEM
*/
int btfm_slim_enable_ch(struct btfmslim *btfmslim,
struct btfmslim_ch *ch, uint8_t rxport, uint32_t rates,
uint8_t nchan);
/**
* btfm_slim_disable_ch: disable channel for slimbus slave port
* @btfmslim: slimbus slave device data pointer.
* @ch: slimbus slave channel pointer
* @rxport: rxport or txport
* @nChan: number of chaneels.
* Returns:
* -EINVAL
* -ETIMEDOUT
* -ENOMEM
*/
int btfm_slim_disable_ch(struct btfmslim *btfmslim,
struct btfmslim_ch *ch, uint8_t rxport, uint8_t nchan);
/**
* btfm_slim_register_codec: Register codec driver in slimbus device node
* @btfmslim: slimbus slave device data pointer.
* Returns:
* -ENOMEM
* 0
*/
int btfm_slim_register_codec(struct btfmslim *btfmslim);
/**
* btfm_slim_unregister_codec: Unregister codec driver in slimbus device node
* @dev: device node
* Returns:
* VOID
*/
void btfm_slim_unregister_codec(struct device *dev);
#endif /* BTFM_SLIM_H */

View File

@@ -0,0 +1,466 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 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/slimbus.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "btfm_slim.h"
static int bt_soc_enable_status;
int btfm_feedback_ch_setting;
static int btfm_slim_codec_write(struct snd_soc_component *codec,
unsigned int reg, unsigned int value)
{
BTFMSLIM_DBG("");
return 0;
}
static unsigned int btfm_slim_codec_read(struct snd_soc_component *codec,
unsigned int reg)
{
BTFMSLIM_DBG("");
return 0;
}
static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_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)
{
BTFMSLIM_DBG("");
return 1;
}
static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_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)
{
BTFMSLIM_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_slim_codec_probe(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
snd_soc_add_component_controls(codec, status_controls,
ARRAY_SIZE(status_controls));
return 0;
}
static void btfm_slim_codec_remove(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
}
static int btfm_slim_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret = -1;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
BTFMSLIM_DBG("substream = %s stream = %d dai->name = %s",
substream->name, substream->stream, dai->name);
ret = btfm_slim_hw_init(btfmslim);
return ret;
}
static void btfm_slim_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int i;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
struct btfmslim_ch *ch;
uint8_t rxport, nchan = 1;
BTFMSLIM_DBG("dai->name: %s, dai->id: %d, dai->rate: %d", dai->name,
dai->id, dai->rate);
switch (dai->id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("dai->id is invalid:%d", dai->id);
return;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != dai->id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return;
}
btfm_slim_disable_ch(btfmslim, ch, rxport, nchan);
btfm_slim_hw_deinit(btfmslim);
}
static int btfm_slim_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct btfmslim *btfmslim;
btfmslim = snd_soc_component_get_drvdata(dai->component);
btfmslim->bps = params_width(params);
btfmslim->direction = substream->stream;
BTFMSLIM_DBG("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_slim_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret = -EINVAL;
int i = 0;
struct btfmslim_ch *ch;
uint8_t rxport, nchan = 1;
struct btfmslim *btfmslim;
btfmslim = snd_soc_component_get_drvdata(dai->component);
btfmslim->direction = substream->stream;
bt_soc_enable_status = 0;
BTFMSLIM_INFO("dai->name: %s, dai->id: %d, dai->rate: %d direction: %d", dai->name,
dai->id, dai->rate, btfmslim->direction);
/* save sample rate */
btfmslim->sample_rate = dai->rate;
switch (dai->id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("dai->id is invalid:%d", dai->id);
return ret;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != dai->id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return ret;
}
ret = btfm_slim_enable_ch(btfmslim, ch, rxport, dai->rate, nchan);
/* save the enable channel status */
if (ret == 0)
bt_soc_enable_status = 1;
if (ret == -EISCONN) {
BTFMSLIM_ERR("channel opened without closing, returning success");
ret = 0;
}
return ret;
}
/* This function will be called once during boot up */
static int btfm_slim_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)
{
int ret = 0, i;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
rx_chs = btfmslim->rx_chs;
tx_chs = btfmslim->tx_chs;
if (!rx_chs || !tx_chs)
return ret;
BTFMSLIM_DBG("Rx: id\tname\tport\tch");
for (i = 0; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < rx_num);
i++, rx_chs++) {
/* Set Rx Channel number from machine driver and
* get channel handler from slimbus driver
*/
rx_chs->ch = *(uint8_t *)(rx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%d\t", rx_chs->id,
rx_chs->name, rx_chs->port, rx_chs->ch);
}
BTFMSLIM_DBG("Tx: id\tname\tport\tch");
for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < tx_num);
i++, tx_chs++) {
/* Set Tx Channel number from machine driver and
* get channel handler from slimbus driver
*/
tx_chs->ch = *(uint8_t *)(tx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%d\t", tx_chs->id,
tx_chs->name, tx_chs->port, tx_chs->ch);
}
return ret;
}
static int btfm_slim_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)
{
int i, ret = -EINVAL, *slot = NULL, j = 0, num = 1;
struct btfmslim *btfmslim = snd_soc_component_get_drvdata(dai->component);
struct btfmslim_ch *ch = NULL;
if (!btfmslim)
return ret;
switch (dai->id) {
case BTFM_FM_SLIM_TX:
num = 2;
/* fall through */
fallthrough;
case BTFM_BT_SCO_SLIM_TX:
if (!tx_slot || !tx_num) {
BTFMSLIM_ERR("Invalid tx_slot %p or tx_num %p",
tx_slot, tx_num);
return -EINVAL;
}
ch = btfmslim->tx_chs;
if (!ch)
return -EINVAL;
slot = tx_slot;
*rx_slot = 0;
*tx_num = num;
*rx_num = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
if (!rx_slot || !rx_num) {
BTFMSLIM_ERR("Invalid rx_slot %p or rx_num %p",
rx_slot, rx_num);
return -EINVAL;
}
ch = btfmslim->rx_chs;
if (!ch)
return -EINVAL;
slot = rx_slot;
*tx_slot = 0;
*tx_num = 0;
*rx_num = num;
break;
default:
BTFMSLIM_ERR("Unsupported DAI %d", dai->id);
return -EINVAL;
}
do {
if (!ch)
return -EINVAL;
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id !=
BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id != dai->id);
ch++, i++)
;
if (ch->id == BTFM_SLIM_NUM_CODEC_DAIS ||
i == BTFM_SLIM_NUM_CODEC_DAIS) {
BTFMSLIM_ERR(
"No channel has been allocated for dai (%d)",
dai->id);
return -EINVAL;
}
if (!slot)
return -EINVAL;
*(slot + j) = ch->ch;
BTFMSLIM_DBG("id:%d, port:%d, ch:%d, slot: %d", ch->id,
ch->port, ch->ch, *(slot + j));
/* In case it has mulitiple channels */
if (++j < num)
ch++;
} while (j < num);
return 0;
}
static struct snd_soc_dai_ops btfmslim_dai_ops = {
.startup = btfm_slim_dai_startup,
.shutdown = btfm_slim_dai_shutdown,
.hw_params = btfm_slim_dai_hw_params,
.prepare = btfm_slim_dai_prepare,
.set_channel_map = btfm_slim_dai_set_channel_map,
.get_channel_map = btfm_slim_dai_get_channel_map,
};
static struct snd_soc_dai_driver btfmslim_dai[] = {
{ /* FM Audio data multiple channel : FM -> qdsp */
.name = "btfm_fm_slim_tx",
.id = BTFM_FM_SLIM_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 = &btfmslim_dai_ops,
},
{ /* Bluetooth SCO voice uplink: bt -> lpass */
.name = "btfm_bt_sco_slim_tx",
.id = BTFM_BT_SCO_SLIM_TX,
.capture = {
.stream_name = "SCO TX Capture",
/* 8 KHz or 16 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 = &btfmslim_dai_ops,
},
{ /* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
.name = "btfm_bt_sco_a2dp_slim_rx",
.id = BTFM_BT_SCO_A2DP_SLIM_RX,
.playback = {
.stream_name = "SCO A2DP RX Playback",
/* 8/16/44.1/48/88.2/96 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 = &btfmslim_dai_ops,
},
{ /* Bluetooth Split A2DP data: qdsp -> bt */
.name = "btfm_bt_split_a2dp_slim_rx",
.id = BTFM_BT_SPLIT_A2DP_SLIM_RX,
.playback = {
.stream_name = "SPLIT A2DP Playback",
.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 = 1,
},
.ops = &btfmslim_dai_ops,
},
};
static const struct snd_soc_component_driver btfmslim_codec = {
.probe = btfm_slim_codec_probe,
.remove = btfm_slim_codec_remove,
.read = btfm_slim_codec_read,
.write = btfm_slim_codec_write,
};
int btfm_slim_register_codec(struct btfmslim *btfm_slim)
{
int ret = 0;
struct device *dev = btfm_slim->dev;
BTFMSLIM_DBG("");
dev_err(dev, "\n");
/* Register Codec driver */
ret = snd_soc_register_component(dev, &btfmslim_codec,
btfmslim_dai, ARRAY_SIZE(btfmslim_dai));
if (ret)
BTFMSLIM_ERR("failed to register codec (%d)", ret);
return ret;
}
void btfm_slim_unregister_codec(struct device *dev)
{
BTFMSLIM_DBG("");
/* Unregister Codec driver */
snd_soc_unregister_component(dev);
}
MODULE_DESCRIPTION("BTFM Slimbus Codec driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,547 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021, 2023-2024 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/slimbus.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "btfm_slim.h"
#include "btfm_slim_hw_interface.h"
#include "btfm_codec_hw_interface.h"
static int bt_soc_enable_status;
int btfm_feedback_ch_setting;
static uint8_t usecase_codec;
static int btfm_slim_hwep_write(struct snd_soc_component *codec,
unsigned int reg, unsigned int value)
{
BTFMSLIM_DBG("");
return 0;
}
static unsigned int btfm_slim_hwep_read(struct snd_soc_component *codec,
unsigned int reg)
{
BTFMSLIM_DBG("");
return 0;
}
static int btfm_soc_status_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_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)
{
BTFMSLIM_DBG("");
return 1;
}
static int btfm_get_feedback_ch_setting(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_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)
{
BTFMSLIM_DBG("");
btfm_feedback_ch_setting = ucontrol->value.integer.value[0];
return 1;
}
static int btfm_get_codec_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
BTFMSLIM_DBG("current codec type:%s", codec_text[usecase_codec]);
ucontrol->value.integer.value[0] = usecase_codec;
return 1;
}
static int btfm_put_codec_type(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
usecase_codec = ucontrol->value.integer.value[0];
BTFMSLIM_DBG("codec type set to:%s", codec_text[usecase_codec]);
return 1;
}
static 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),
SOC_ENUM_EXT("BT codec type", codec_display,
btfm_get_codec_type, btfm_put_codec_type),
};
static int btfm_slim_hwep_probe(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
return 0;
}
static void btfm_slim_hwep_remove(struct snd_soc_component *codec)
{
BTFMSLIM_DBG("");
}
static int btfm_slim_dai_startup(void *dai)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
int ret = -1;
BTFMSLIM_DBG("");
ret = btfm_slim_hw_init(btfmslim);
return ret;
}
static void btfm_slim_dai_shutdown(void *dai, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct btfmslim_ch *ch;
int i;
uint8_t rxport, nchan = 1;
BTFMSLIM_DBG("");
switch (id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("id is invalid:%d", id);
return;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return;
}
btfm_slim_disable_ch(btfmslim, ch, rxport, nchan);
btfm_slim_hw_deinit(btfmslim);
}
static int btfm_slim_dai_hw_params(void *dai, uint32_t bps,
uint32_t direction,
uint8_t num_channels) {
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
BTFMSLIM_DBG("");
btfmslim->bps = bps;
btfmslim->direction = direction;
return 0;
}
void btfm_get_sampling_rate(uint32_t *sampling_rate)
{
uint8_t codec_types_avb = ARRAY_SIZE(codec_text);
if (usecase_codec > (codec_types_avb - 1)) {
BTFMSLIM_ERR("falling back to use default sampling_rate: %u",
*sampling_rate);
return;
}
if (*sampling_rate == 44100 || *sampling_rate == 48000) {
if (usecase_codec == LDAC ||
usecase_codec == APTX_AD)
*sampling_rate = (*sampling_rate) *2;
}
if (usecase_codec == LC3_VOICE ||
usecase_codec == APTX_AD_SPEECH ||
usecase_codec == LC3 || usecase_codec == APTX_AD_R4 ||
usecase_codec == RVP) {
*sampling_rate = 96000;
}
if (usecase_codec == APTX_AD_QLEA)
*sampling_rate = 192000;
BTFMSLIM_INFO("current usecase codec type %s and sampling rate:%u khz",
codec_text[usecase_codec], *sampling_rate);
}
static int btfm_slim_dai_prepare(void *dai, uint32_t sampling_rate, uint32_t direction, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct btfmslim_ch *ch;
int ret = -EINVAL;
int i = 0;
uint8_t rxport, nchan = 1;
btfmslim->direction = direction;
bt_soc_enable_status = 0;
btfm_get_sampling_rate(&sampling_rate);
/* save sample rate */
btfmslim->sample_rate = sampling_rate;
switch (id) {
case BTFM_FM_SLIM_TX:
nchan = 2;
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
rxport = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
rxport = 1;
break;
case BTFM_SLIM_NUM_CODEC_DAIS:
default:
BTFMSLIM_ERR("id is invalid:%d", id);
return ret;
}
/* Search for dai->id matched port handler */
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != BTFM_SLIM_NUM_CODEC_DAIS) &&
(ch->id != id); ch++, i++)
;
if ((ch->port == BTFM_SLIM_PGD_PORT_LAST) ||
(ch->id == BTFM_SLIM_NUM_CODEC_DAIS)) {
BTFMSLIM_ERR("ch is invalid!!");
return ret;
}
ret = btfm_slim_enable_ch(btfmslim, ch, rxport, sampling_rate, nchan);
/* save the enable channel status */
if (ret == 0)
bt_soc_enable_status = 1;
if (ret == -EISCONN) {
BTFMSLIM_ERR("channel opened without closing, returning success");
ret = 0;
}
return ret;
}
/* This function will be called once during boot up */
static int btfm_slim_dai_set_channel_map(void *dai,
unsigned int tx_num, unsigned int *tx_slot,
unsigned int rx_num, unsigned int *rx_slot)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct btfmslim_ch *rx_chs;
struct btfmslim_ch *tx_chs;
int ret = 0, i;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
rx_chs = btfmslim->rx_chs;
tx_chs = btfmslim->tx_chs;
if (!rx_chs || !tx_chs)
return ret;
BTFMSLIM_DBG("Rx: id\tname\tport\tch");
for (i = 0; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < rx_num);
i++, rx_chs++) {
/* Set Rx Channel number from machine driver and
* get channel handler from slimbus driver
*/
rx_chs->ch = *(uint8_t *)(rx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", rx_chs->id,
rx_chs->name, rx_chs->port, rx_chs->ch);
}
BTFMSLIM_DBG("Tx: id\tname\tport\tch");
for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) && (i < tx_num);
i++, tx_chs++) {
/* Set Tx Channel number from machine driver and
* get channel handler from slimbus driver
*/
tx_chs->ch = *(uint8_t *)(tx_slot + i);
BTFMSLIM_DBG(" %d\t%s\t%d\t%x", tx_chs->id,
tx_chs->name, tx_chs->port, tx_chs->ch);
}
return ret;
}
static int btfm_slim_dai_get_channel_map(void *dai,
unsigned int *tx_num, unsigned int *tx_slot,
unsigned int *rx_num, unsigned int *rx_slot, int id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
int i, ret = -EINVAL, *slot = NULL, j = 0, num = 1;
struct btfmslim_ch *ch = NULL;
BTFMSLIM_DBG("");
if (!btfmslim)
return ret;
switch (id) {
case BTFM_FM_SLIM_TX:
num = 2;
fallthrough;
case BTFM_BT_SCO_SLIM_TX:
if (!tx_slot || !tx_num) {
BTFMSLIM_ERR("Invalid tx_slot %p or tx_num %p",
tx_slot, tx_num);
return -EINVAL;
}
ch = btfmslim->tx_chs;
if (!ch)
return -EINVAL;
slot = tx_slot;
*rx_slot = 0;
*tx_num = num;
*rx_num = 0;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
if (!rx_slot || !rx_num) {
BTFMSLIM_ERR("Invalid rx_slot %p or rx_num %p",
rx_slot, rx_num);
return -EINVAL;
}
ch = btfmslim->rx_chs;
if (!ch)
return -EINVAL;
slot = rx_slot;
*tx_slot = 0;
*tx_num = 0;
*rx_num = num;
break;
default:
BTFMSLIM_ERR("Unsupported DAI %d", id);
return -EINVAL;
}
do {
if (!ch)
return -EINVAL;
for (i = 0; (i < BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id !=
BTFM_SLIM_NUM_CODEC_DAIS) && (ch->id != id);
ch++, i++)
;
if (ch->id == BTFM_SLIM_NUM_CODEC_DAIS ||
i == BTFM_SLIM_NUM_CODEC_DAIS) {
BTFMSLIM_ERR(
"No channel has been allocated for dai (%d)",
id);
return -EINVAL;
}
if (!slot)
return -EINVAL;
*(slot + j) = ch->ch;
BTFMSLIM_DBG("id:%d, port:%d, ch:%d, slot: %d", ch->id,
ch->port, ch->ch, *(slot + j));
/* In case it has mulitiple channels */
if (++j < num)
ch++;
} while (j < num);
return 0;
}
int btfm_slim_dai_get_configs(void *dai, void *config, uint8_t id)
{
struct hwep_data *hwep_info = (struct hwep_data *)dai;
struct btfmslim *btfmslim = dev_get_drvdata(hwep_info->dev);
struct master_hwep_configurations *hwep_config;
struct btfmslim_ch *ch = NULL;
int i = 0;
BTFMSLIM_DBG("");
hwep_config = (struct master_hwep_configurations *) config;
hwep_config->stream_id = id;
hwep_config->device_id = btfmslim->device_id;
hwep_config->sample_rate = btfmslim->sample_rate;
hwep_config->bit_width = (uint8_t)btfmslim->bps;
hwep_config->codectype = usecase_codec;
hwep_config->direction = btfmslim->direction;
switch (id) {
case BTFM_FM_SLIM_TX:
case BTFM_BT_SCO_SLIM_TX:
ch = btfmslim->tx_chs;
break;
case BTFM_BT_SCO_A2DP_SLIM_RX:
case BTFM_BT_SPLIT_A2DP_SLIM_RX:
ch = btfmslim->rx_chs;
break;
}
for (; i < id ; i++) {
if (ch[i].id == id) {
BTFMSLIM_DBG("id matched");
hwep_config->num_channels = 1;
hwep_config->chan_num = ch[i].ch;
break;
}
}
return 1;
}
static struct hwep_dai_ops btfmslim_hw_dai_ops = {
.hwep_startup = btfm_slim_dai_startup,
.hwep_shutdown = btfm_slim_dai_shutdown,
.hwep_hw_params = btfm_slim_dai_hw_params,
.hwep_prepare = btfm_slim_dai_prepare,
.hwep_set_channel_map = btfm_slim_dai_set_channel_map,
.hwep_get_channel_map = btfm_slim_dai_get_channel_map,
.hwep_get_configs = btfm_slim_dai_get_configs,
.hwep_codectype = &usecase_codec,
};
static struct hwep_dai_driver btfmslim_dai_driver[] = {
{ /* Bluetooth SCO voice uplink: bt -> lpass */
.dai_name = "btaudio_tx",
.id = BTAUDIO_TX,
.capture = {
.stream_name = "BT Audio Slim Tx Capture",
/* 8 KHz or 16 KHz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_8000_192000
| 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,
},
.dai_ops = &btfmslim_hw_dai_ops,
},
{ /* Bluetooth SCO voice downlink: lpass -> bt or A2DP Playback */
.dai_name = "btaudio_rx",
.id = BTAUDIO_RX,
.playback = {
.stream_name = "BT Audio Slim Rx Playback",
/* 8/16/44.1/48/88.2/96 Khz */
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000
| SNDRV_PCM_RATE_8000_192000
| 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,
},
.dai_ops = &btfmslim_hw_dai_ops,
},
};
static struct hwep_comp_drv btfmslim_hw_driver = {
.hwep_probe = btfm_slim_hwep_probe,
.hwep_remove = btfm_slim_hwep_remove,
.hwep_read = btfm_slim_hwep_read,
.hwep_write = btfm_slim_hwep_write,
};
int btfm_slim_register_hw_ep(struct btfmslim *btfm_slim)
{
struct device *dev = btfm_slim->dev;
struct hwep_data *hwep_info;
int ret = 0;
BTFMSLIM_INFO("Registering with BTFMCODEC HWEP interface\n");
hwep_info = kzalloc(sizeof(struct hwep_data), GFP_KERNEL);
if (!hwep_info) {
BTFMSLIM_ERR("%s: failed to allocate memory\n", __func__);
ret = -ENOMEM;
goto end;
}
/* Copy EP device parameters as intercations will be on the same device */
hwep_info->dev = dev;
strscpy(hwep_info->driver_name, BTFMSLIM_DEV_NAME, DEVICE_NAME_MAX_LEN);
hwep_info->drv = &btfmslim_hw_driver;
hwep_info->dai_drv = btfmslim_dai_driver;
hwep_info->num_dai = ARRAY_SIZE(btfmslim_dai_driver);
hwep_info->num_dai = 2;
hwep_info->num_mixer_ctrl = ARRAY_SIZE(status_controls);
hwep_info->mixer_ctrl = status_controls;
/* Register to hardware endpoint */
ret = btfmcodec_register_hw_ep(hwep_info);
if (ret) {
BTFMSLIM_ERR("failed to register with btfmcodec driver hw interface (%d)", ret);
goto end;
}
BTFMSLIM_INFO("Registered succesfull with BTFMCODEC HWEP interface\n");
return ret;
end:
return ret;
}
void btfm_slim_unregister_hwep(void)
{
BTFMSLIM_INFO("Unregistered with BTFMCODEC HWEP interface");
/* Unregister with BTFMCODEC HWEP driver */
btfmcodec_unregister_hw_ep(BTFMSLIM_DEV_NAME);
}
MODULE_DESCRIPTION("BTFM Slimbus driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __LINUX_BTFM_SLIM_HW_INTERFACE_H
#define __LINUX_BTFM_SLIM_HW_INTERFACE_H
// Todo protect with flags
int btfm_slim_register_hw_ep(struct btfmslim *btfm_slim);
void btfm_slim_unregister_hwep(void);
/* Codec driver defines */
enum {
BTAUDIO_TX = 1,
BTAUDIO_RX = 2,
BTAUDIO_NUM_CODEC_DAIS
};
typedef enum Codec {
SBC = 0,
AAC,
LDAC,
APTX,
APTX_HD,
APTX_AD,
LC3,
APTX_AD_SPEECH,
LC3_VOICE,
APTX_AD_QLEA,
APTX_AD_R4,
RVP,
SSC,
LHDC,
NO_CODEC
} codectype;
static char const *codec_text[] = {"CODEC_TYPE_SBC", "CODEC_TYPE_AAC",
"CODEC_TYPE_LDAC", "CODEC_TYPE_APTX",
"CODEC_TYPE_APTX_HD", "CODEC_TYPE_APTX_AD",
"CODEC_TYPE_LC3", "CODEC_TYPE_APTX_AD_SPEECH",
"CODEC_TYPE_LC3_VOICE", "CODEC_TYPE_APTX_AD_QLEA",
"CODEC_TYPE_APTX_AD_R4",
"CODEC_TYPE_RVP", "CODEC_TYPE_SSC",
"CODEC_TYPE_LHDC", "CODEC_TYPE_INVALID"};
static SOC_ENUM_SINGLE_EXT_DECL(codec_display, codec_text);
#endif /*__LINUX_BTFM_SLIM_HW_INTERFACE_H*/

View File

@@ -0,0 +1,187 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/slimbus.h>
#include "btfm_slim.h"
#include "btfm_slim_slave.h"
/* SLAVE (WCN3990/QCA6390) Port assignment */
struct btfmslim_ch slave_rxport[] = {
{.id = BTFM_BT_SCO_A2DP_SLIM_RX, .name = "SCO_A2P_Rx",
.port = SLAVE_SB_PGD_PORT_RX_SCO},
{.id = BTFM_BT_SPLIT_A2DP_SLIM_RX, .name = "A2P_Rx",
.port = SLAVE_SB_PGD_PORT_RX_A2P},
{.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "",
.port = BTFM_SLIM_PGD_PORT_LAST},
};
struct btfmslim_ch slave_txport[] = {
{.id = BTFM_BT_SCO_SLIM_TX, .name = "SCO_Tx",
.port = SLAVE_SB_PGD_PORT_TX_SCO},
{.id = BTFM_FM_SLIM_TX, .name = "FM_Tx1",
.port = SLAVE_SB_PGD_PORT_TX1_FM},
{.id = BTFM_FM_SLIM_TX, .name = "FM_Tx2",
.port = SLAVE_SB_PGD_PORT_TX2_FM},
{.id = BTFM_SLIM_NUM_CODEC_DAIS, .name = "",
.port = BTFM_SLIM_PGD_PORT_LAST},
};
/* Function description */
int btfm_slim_slave_hw_init(struct btfmslim *btfmslim)
{
int ret = 0;
uint32_t reg;
BTFMSLIM_DBG("");
if (!btfmslim)
return -EINVAL;
/* Get SB_SLAVE_HW_REV_MSB value*/
reg = SLAVE_SB_SLAVE_HW_REV_MSB;
ret = btfm_slim_read(btfmslim, reg, IFD);
if (ret < 0)
BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
BTFMSLIM_DBG("Major Rev: 0x%x, Minor Rev: 0x%x",
(ret & 0xF0) >> 4, (ret & 0x0F));
/* Get SB_SLAVE_HW_REV_LSB value*/
reg = SLAVE_SB_SLAVE_HW_REV_LSB;
ret = btfm_slim_read(btfmslim, reg, IFD);
if (ret < 0)
BTFMSLIM_ERR("failed to read (%d) reg 0x%x", ret, reg);
else {
BTFMSLIM_INFO("read (%d) reg 0x%x", ret, reg);
ret = 0;
}
return ret;
}
static inline int is_fm_port(uint8_t port_num)
{
if (port_num == SLAVE_SB_PGD_PORT_TX1_FM ||
port_num == CHRKVER3_SB_PGD_PORT_TX1_FM ||
port_num == CHRKVER3_SB_PGD_PORT_TX2_FM ||
port_num == SLAVE_SB_PGD_PORT_TX2_FM)
return 1;
else
return 0;
}
int btfm_slim_slave_enable_port(struct btfmslim *btfmslim, uint8_t port_num,
uint8_t rxport, uint8_t enable)
{
int ret = 0;
uint8_t reg_val = 0, en;
uint8_t rxport_num = 0;
uint16_t reg;
BTFMSLIM_DBG("port(%d) enable(%d)", port_num, enable);
if (rxport) {
BTFMSLIM_DBG("sample rate is %d", btfmslim->sample_rate);
if (enable &&
btfmslim->sample_rate != 44100 &&
btfmslim->sample_rate != 88200) {
BTFMSLIM_DBG("setting multichannel bit");
/* For SCO Rx, A2DP Rx other than 44.1 and 88.2Khz */
if (port_num < 24) {
rxport_num = port_num - 16;
reg_val = 0x01 << rxport_num;
reg = SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_0(
rxport_num);
} else {
rxport_num = port_num - 24;
reg_val = 0x01 << rxport_num;
reg = SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_1(
rxport_num);
}
BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)",
reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x",
ret, reg);
goto error;
}
}
/* Port enable */
reg = SLAVE_SB_PGD_PORT_RX_CFGN(port_num - 0x10);
goto enable_disable_rxport;
}
if (!enable)
goto enable_disable_txport;
/* txport */
/* Multiple Channel Setting */
if (is_fm_port(port_num)) {
if (port_num == CHRKVER3_SB_PGD_PORT_TX1_FM)
reg_val = (0x1 << CHRKVER3_SB_PGD_PORT_TX1_FM);
else if (port_num == CHRKVER3_SB_PGD_PORT_TX2_FM)
reg_val = (0x1 << CHRKVER3_SB_PGD_PORT_TX2_FM);
else
reg_val = (0x1 << SLAVE_SB_PGD_PORT_TX1_FM) |
(0x1 << SLAVE_SB_PGD_PORT_TX2_FM);
reg = SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num);
BTFMSLIM_INFO("writing reg_val (%d) to reg(%x)", reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
goto error;
}
} else if (port_num == SLAVE_SB_PGD_PORT_TX_SCO) {
/* SCO Tx */
reg_val = 0x1 << SLAVE_SB_PGD_PORT_TX_SCO;
reg = SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(port_num);
BTFMSLIM_DBG("writing reg_val (%d) to reg(%x)",
reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x",
ret, reg);
goto error;
}
}
/* Enable Tx port hw auto recovery for underrun or overrun error */
reg_val = (SLAVE_ENABLE_OVERRUN_AUTO_RECOVERY |
SLAVE_ENABLE_UNDERRUN_AUTO_RECOVERY);
reg = SLAVE_SB_PGD_PORT_TX_OR_UR_CFGN(port_num);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0) {
BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
goto error;
}
enable_disable_txport:
/* Port enable */
reg = SLAVE_SB_PGD_PORT_TX_CFGN(port_num);
enable_disable_rxport:
if (enable)
en = SLAVE_SB_PGD_PORT_ENABLE;
else
en = SLAVE_SB_PGD_PORT_DISABLE;
if (is_fm_port(port_num))
reg_val = en | SLAVE_SB_PGD_PORT_WM_L8;
else if (port_num == SLAVE_SB_PGD_PORT_TX_SCO)
reg_val = enable ? en | SLAVE_SB_PGD_PORT_WM_L1 : en;
else
reg_val = enable ? en | SLAVE_SB_PGD_PORT_WM_LB : en;
if (enable && port_num == SLAVE_SB_PGD_PORT_TX_SCO)
BTFMSLIM_INFO("programming SCO Tx with reg_val %d to reg 0x%x",
reg_val, reg);
ret = btfm_slim_write(btfmslim, reg, reg_val, IFD);
if (ret < 0)
BTFMSLIM_ERR("failed to write (%d) reg 0x%x", ret, reg);
error:
return ret;
}

View File

@@ -0,0 +1,187 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef BTFM_SLIM_SLAVE_H
#define BTFM_SLIM_SLAVE_H
#include "btfm_slim.h"
/* Registers Address */
#define SLAVE_SB_COMP_TEST 0x00000000
#define SLAVE_SB_SLAVE_HW_REV_MSB 0x00000001
#define SLAVE_SB_SLAVE_HW_REV_LSB 0x00000002
#define SLAVE_SB_DEBUG_FEATURES 0x00000005
#define SLAVE_SB_INTF_INT_EN 0x00000010
#define SLAVE_SB_INTF_INT_STATUS 0x00000011
#define SLAVE_SB_INTF_INT_CLR 0x00000012
#define SLAVE_SB_FRM_CFG 0x00000013
#define SLAVE_SB_FRM_STATUS 0x00000014
#define SLAVE_SB_FRM_INT_EN 0x00000015
#define SLAVE_SB_FRM_INT_STATUS 0x00000016
#define SLAVE_SB_FRM_INT_CLR 0x00000017
#define SLAVE_SB_FRM_WAKEUP 0x00000018
#define SLAVE_SB_FRM_CLKCTL_DONE 0x00000019
#define SLAVE_SB_FRM_IE_STATUS 0x0000001A
#define SLAVE_SB_FRM_VE_STATUS 0x0000001B
#define SLAVE_SB_PGD_TX_CFG_STATUS 0x00000020
#define SLAVE_SB_PGD_RX_CFG_STATUS 0x00000021
#define SLAVE_SB_PGD_DEV_INT_EN 0x00000022
#define SLAVE_SB_PGD_DEV_INT_STATUS 0x00000023
#define SLAVE_SB_PGD_DEV_INT_CLR 0x00000024
#define SLAVE_SB_PGD_PORT_INT_EN_RX_0 0x00000030
#define SLAVE_SB_PGD_PORT_INT_EN_RX_1 0x00000031
#define SLAVE_SB_PGD_PORT_INT_EN_TX_0 0x00000032
#define SLAVE_SB_PGD_PORT_INT_EN_TX_1 0x00000033
#define SLAVE_SB_PGD_PORT_INT_STATUS_RX_0 0x00000034
#define SLAVE_SB_PGD_PORT_INT_STATUS_RX_1 0x00000035
#define SLAVE_SB_PGD_PORT_INT_STATUS_TX_0 0x00000036
#define SLAVE_SB_PGD_PORT_INT_STATUS_TX_1 0x00000037
#define SLAVE_SB_PGD_PORT_INT_CLR_RX_0 0x00000038
#define SLAVE_SB_PGD_PORT_INT_CLR_RX_1 0x00000039
#define SLAVE_SB_PGD_PORT_INT_CLR_TX_0 0x0000003A
#define SLAVE_SB_PGD_PORT_INT_CLR_TX_1 0x0000003B
#define SLAVE_SB_PGD_PORT_RX_CFGN(n) (0x00000040 + n)
#define SLAVE_SB_PGD_PORT_TX_CFGN(n) (0x00000050 + n)
#define SLAVE_SB_PGD_PORT_INT_RX_SOURCEN(n) (0x00000060 + n)
#define SLAVE_SB_PGD_PORT_INT_TX_SOURCEN(n) (0x00000070 + n)
#define SLAVE_SB_PGD_PORT_RX_STATUSN(n) (0x00000080 + n)
#define SLAVE_SB_PGD_PORT_TX_STATUSN(n) (0x00000090 + n)
#define SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_0(n) (0x00000100 + 0x4*n)
#define SLAVE_SB_PGD_TX_PORTn_MULTI_CHNL_1(n) (0x00000101 + 0x4*n)
#define SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_0(n) (0x00000180 + 0x4*n)
#define SLAVE_SB_PGD_RX_PORTn_MULTI_CHNL_1(n) (0x00000181 + 0x4*n)
#define SLAVE_SB_PGD_PORT_TX_OR_UR_CFGN(n) (0x000001F0 + n)
/* 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 SLAVE_SB_PGD_PORT_WM_L1 (0x1 << 1)
#define SLAVE_SB_PGD_PORT_WM_L2 (0x2 << 1)
#define SLAVE_SB_PGD_PORT_WM_L3 (0x3 << 1)
#define SLAVE_SB_PGD_PORT_WM_L8 (0x8 << 1)
#define SLAVE_SB_PGD_PORT_WM_LB (0xB << 1)
#define SLAVE_SB_PGD_PORT_RX_NUM 16
#define SLAVE_SB_PGD_PORT_TX_NUM 16
/* PGD Port Map */
#define SLAVE_SB_PGD_PORT_TX_SCO 0
#define SLAVE_SB_PGD_PORT_TX1_FM 1
#define SLAVE_SB_PGD_PORT_TX2_FM 2
#define CHRKVER3_SB_PGD_PORT_TX1_FM 5
#define CHRKVER3_SB_PGD_PORT_TX2_FM 4
#define SLAVE_SB_PGD_PORT_RX_SCO 16
#define SLAVE_SB_PGD_PORT_RX_A2P 17
enum {
QCA_CHEROKEE_SOC_ID_0200 = 0x40010200,
QCA_CHEROKEE_SOC_ID_0201 = 0x40010201,
QCA_CHEROKEE_SOC_ID_0210 = 0x40010214,
QCA_CHEROKEE_SOC_ID_0211 = 0x40010224,
QCA_CHEROKEE_SOC_ID_0310 = 0x40010310,
QCA_CHEROKEE_SOC_ID_0320 = 0x40010320,
QCA_CHEROKEE_SOC_ID_0320_UMC = 0x40014320,
};
enum {
QCA_APACHE_SOC_ID_0100 = 0x40020120,
QCA_APACHE_SOC_ID_0110 = 0x40020130,
QCA_APACHE_SOC_ID_0120 = 0x40020140,
QCA_APACHE_SOC_ID_0121 = 0x40020150,
};
enum {
QCA_COMANCHE_SOC_ID_0101 = 0x40070101,
QCA_COMANCHE_SOC_ID_0110 = 0x40070110,
QCA_COMANCHE_SOC_ID_0120 = 0x40070120,
QCA_COMANCHE_SOC_ID_0130 = 0x40070130,
QCA_COMANCHE_SOC_ID_4130 = 0x40074130,
QCA_COMANCHE_SOC_ID_5120 = 0x40075120,
QCA_COMANCHE_SOC_ID_5130 = 0x40075130,
};
enum {
QCA_HASTINGS_SOC_ID_0200 = 0x400A0200,
};
enum {
QCA_HSP_SOC_ID_0100 = 0x400C0100,
QCA_HSP_SOC_ID_0110 = 0x400C0110,
QCA_HSP_SOC_ID_0200 = 0x400C0200,
QCA_HSP_SOC_ID_0210 = 0x400C0210,
QCA_HSP_SOC_ID_1201 = 0x400C1201,
QCA_HSP_SOC_ID_1211 = 0x400C1211,
};
enum {
QCA_MOSELLE_SOC_ID_0100 = 0x40140100,
QCA_MOSELLE_SOC_ID_0110 = 0x40140110,
QCA_MOSELLE_SOC_ID_0120 = 0x40140120,
};
enum {
QCA_HAMILTON_SOC_ID_0100 = 0x40170100,
QCA_HAMILTON_SOC_ID_0101 = 0x40170101,
QCA_HAMILTON_SOC_ID_0200 = 0x40170200,
};
/* Function Prototype */
/*
* btfm_slim_slave_hw_init: Initialize slave specific slimbus slave device
* @btfmslim: slimbus slave device data pointer.
* Returns:
* 0: Success
* else: Fail
*/
int btfm_slim_slave_hw_init(struct btfmslim *btfmslim);
/*
* btfm_slim_slave_enable_rxport: Enable slave Rx port by given port number
* @btfmslim: slimbus slave device data pointer.
* @portNum: slimbus slave port number to enable
* @rxport: rxport or txport
* @enable: enable port or disable port
* Returns:
* 0: Success
* else: Fail
*/
int btfm_slim_slave_enable_port(struct btfmslim *btfmslim, uint8_t portNum,
uint8_t rxport, uint8_t enable);
/* Specific defines for slave slimbus device */
#define SLAVE_SLIM_REG_OFFSET 0x0800
#ifdef SLIM_SLAVE_REG_OFFSET
#undef SLIM_SLAVE_REG_OFFSET
#define SLIM_SLAVE_REG_OFFSET SLAVE_SLIM_REG_OFFSET
#endif
/* Assign vendor specific function */
extern struct btfmslim_ch slave_txport[];
extern struct btfmslim_ch slave_rxport[];
#ifdef SLIM_SLAVE_RXPORT
#undef SLIM_SLAVE_RXPORT
#define SLIM_SLAVE_RXPORT (&slave_rxport[0])
#endif
#ifdef SLIM_SLAVE_TXPORT
#undef SLIM_SLAVE_TXPORT
#define SLIM_SLAVE_TXPORT (&slave_txport[0])
#endif
#ifdef SLIM_SLAVE_INIT
#undef SLIM_SLAVE_INIT
#define SLIM_SLAVE_INIT btfm_slim_slave_hw_init
#endif
#ifdef SLIM_SLAVE_PORT_EN
#undef SLIM_SLAVE_PORT_EN
#define SLIM_SLAVE_PORT_EN btfm_slim_slave_enable_port
#endif
#endif