267 lines
6.5 KiB
C
267 lines
6.5 KiB
C
/* Bluetooth MICS
|
|
*
|
|
* Copyright (c) 2020 Bose Corporation
|
|
* Copyright (c) 2020-2022 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/sys/check.h>
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/init.h>
|
|
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
#include <zephyr/bluetooth/audio/micp.h>
|
|
#include <zephyr/bluetooth/audio/aics.h>
|
|
|
|
#include "audio_internal.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(bt_micp, CONFIG_BT_MICP_MIC_DEV_LOG_LEVEL);
|
|
|
|
struct bt_micp_server {
|
|
uint8_t mute;
|
|
struct bt_micp_mic_dev_cb *cb;
|
|
struct bt_gatt_service *service_p;
|
|
struct bt_aics *aics_insts[CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT];
|
|
struct k_work_delayable notify_work;
|
|
};
|
|
|
|
static struct bt_micp_server micp_inst;
|
|
|
|
static void mute_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
{
|
|
LOG_DBG("value 0x%04x", value);
|
|
}
|
|
|
|
static ssize_t read_mute(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
LOG_DBG("Mute %u", micp_inst.mute);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
&micp_inst.mute, sizeof(micp_inst.mute));
|
|
}
|
|
|
|
static void notify_work_handler(struct k_work *work)
|
|
{
|
|
struct k_work_delayable *d_work = k_work_delayable_from_work(work);
|
|
struct bt_micp_server *server = CONTAINER_OF(d_work, struct bt_micp_server, notify_work);
|
|
int err;
|
|
|
|
err = bt_gatt_notify_uuid(NULL, BT_UUID_MICS_MUTE, server->service_p->attrs, &server->mute,
|
|
sizeof(server->mute));
|
|
if (err == 0 || err == -ENOTCONN) {
|
|
return;
|
|
}
|
|
|
|
if (err == -ENOMEM &&
|
|
k_work_reschedule(d_work, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US)) >= 0) {
|
|
LOG_WRN("Out of buffers for mute state notification. Will retry in %dms",
|
|
BT_AUDIO_NOTIFY_RETRY_DELAY_US);
|
|
return;
|
|
}
|
|
|
|
LOG_ERR("Mute state notification err %d", err);
|
|
}
|
|
|
|
static ssize_t write_mute(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len, uint16_t offset,
|
|
uint8_t flags)
|
|
{
|
|
const uint8_t *val = buf;
|
|
|
|
if (offset > 0) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
if (len != sizeof(micp_inst.mute)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
if ((conn != NULL && *val == BT_MICP_MUTE_DISABLED) ||
|
|
*val > BT_MICP_MUTE_DISABLED) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
|
|
}
|
|
|
|
if (conn != NULL && micp_inst.mute == BT_MICP_MUTE_DISABLED) {
|
|
return BT_GATT_ERR(BT_MICP_ERR_MUTE_DISABLED);
|
|
}
|
|
|
|
LOG_DBG("%u", *val);
|
|
|
|
if (*val != micp_inst.mute) {
|
|
int err;
|
|
|
|
micp_inst.mute = *val;
|
|
|
|
err = k_work_reschedule(&micp_inst.notify_work, K_NO_WAIT);
|
|
if (err < 0) {
|
|
LOG_ERR("Failed to schedule mute state notification err %d", err);
|
|
}
|
|
|
|
if (micp_inst.cb != NULL && micp_inst.cb->mute != NULL) {
|
|
micp_inst.cb->mute(micp_inst.mute);
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
#define DUMMY_INCLUDE(i, _) BT_GATT_INCLUDE_SERVICE(NULL),
|
|
#define AICS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ())
|
|
|
|
#define BT_MICP_SERVICE_DEFINITION \
|
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_MICS), \
|
|
AICS_INCLUDES(CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT) \
|
|
BT_AUDIO_CHRC(BT_UUID_MICS_MUTE, \
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY, \
|
|
BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \
|
|
read_mute, write_mute, NULL), \
|
|
BT_AUDIO_CCC(mute_cfg_changed)
|
|
|
|
#define MICS_ATTR_COUNT \
|
|
ARRAY_SIZE(((struct bt_gatt_attr []){ BT_MICP_SERVICE_DEFINITION }))
|
|
#define MICS_INCL_COUNT (CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT)
|
|
|
|
static struct bt_gatt_attr mics_attrs[] = { BT_MICP_SERVICE_DEFINITION };
|
|
static struct bt_gatt_service mics_svc;
|
|
|
|
#if defined(CONFIG_BT_MICP_MIC_DEV_AICS)
|
|
static int prepare_aics_inst(struct bt_micp_mic_dev_register_param *param)
|
|
{
|
|
int i;
|
|
int j;
|
|
int err;
|
|
|
|
if (CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT == 0) {
|
|
return 0;
|
|
}
|
|
|
|
for (j = 0, i = 0; i < ARRAY_SIZE(mics_attrs); i++) {
|
|
if (bt_uuid_cmp(mics_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0) {
|
|
micp_inst.aics_insts[j] = bt_aics_free_instance_get();
|
|
if (micp_inst.aics_insts[j] == NULL) {
|
|
LOG_DBG("Could not get free AICS instances[%u]", j);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
err = bt_aics_register(micp_inst.aics_insts[j],
|
|
¶m->aics_param[j]);
|
|
if (err != 0) {
|
|
LOG_DBG("Could not register AICS instance[%u]: %d", j, err);
|
|
return err;
|
|
}
|
|
|
|
mics_attrs[i].user_data = bt_aics_svc_decl_get(micp_inst.aics_insts[j]);
|
|
j++;
|
|
|
|
if (j == CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
__ASSERT(j == CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT,
|
|
"Invalid AICS instance count");
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */
|
|
|
|
/****************************** PUBLIC API ******************************/
|
|
int bt_micp_mic_dev_register(struct bt_micp_mic_dev_register_param *param)
|
|
{
|
|
int err;
|
|
static bool registered;
|
|
|
|
if (registered) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
__ASSERT(param, "MICS register parameter cannot be NULL");
|
|
|
|
#if defined(CONFIG_BT_MICP_MIC_DEV_AICS)
|
|
err = prepare_aics_inst(param);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to prepare AICS instances: %d", err);
|
|
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */
|
|
|
|
mics_svc = (struct bt_gatt_service)BT_GATT_SERVICE(mics_attrs);
|
|
micp_inst.service_p = &mics_svc;
|
|
err = bt_gatt_service_register(&mics_svc);
|
|
|
|
if (err != 0) {
|
|
LOG_ERR("MICS service register failed: %d", err);
|
|
}
|
|
|
|
micp_inst.cb = param->cb;
|
|
|
|
k_work_init_delayable(&micp_inst.notify_work, notify_work_handler);
|
|
|
|
registered = true;
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_micp_mic_dev_mute_disable(void)
|
|
{
|
|
uint8_t val = BT_MICP_MUTE_DISABLED;
|
|
int err;
|
|
|
|
err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0);
|
|
|
|
return err > 0 ? 0 : err;
|
|
}
|
|
|
|
int bt_micp_mic_dev_included_get(struct bt_micp_included *included)
|
|
{
|
|
CHECKIF(included == NULL) {
|
|
LOG_DBG("NULL service pointer");
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_MICP_MIC_DEV_AICS)
|
|
included->aics_cnt = ARRAY_SIZE(micp_inst.aics_insts);
|
|
included->aics = micp_inst.aics_insts;
|
|
#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_micp_mic_dev_unmute(void)
|
|
{
|
|
const uint8_t val = BT_MICP_MUTE_UNMUTED;
|
|
const int err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0);
|
|
|
|
return err > 0 ? 0 : err;
|
|
}
|
|
|
|
int bt_micp_mic_dev_mute(void)
|
|
{
|
|
const uint8_t val = BT_MICP_MUTE_MUTED;
|
|
const int err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0);
|
|
|
|
return err > 0 ? 0 : err;
|
|
}
|
|
|
|
int bt_micp_mic_dev_mute_get(void)
|
|
{
|
|
if (micp_inst.cb && micp_inst.cb->mute) {
|
|
micp_inst.cb->mute(micp_inst.mute);
|
|
}
|
|
|
|
return 0;
|
|
}
|