modules: cirrus_playback: add Speaker Enhancement

add Cirrus Speaker Enhanement driver as DLKM.

Change-Id: Ibad2b4d5f6cac12ce8d6660cb41928ef8562fe10
Signed-off-by: Yoon (Seungyoon) Lee <w22905@motorola.com>
Reviewed-on: https://gerrit.mot.com/1215912
SLTApproved: Slta Waiver
SME-Granted: SME Approvals Granted
Tested-by: Jira Key
Reviewed-by: Rav(CR) Vembu <raviv@motorola.com>
Submit-Approved: Jira Key
This commit is contained in:
Yoon (Seungyoon) Lee 2018-07-26 00:16:02 -05:00 • committed by Seungyoon Lee
commit f20a95ede9
5 changed files with 1119 additions and 0 deletions

View file

@ -0,0 +1,18 @@
Cirrus Logic MSM Framework Driver
Required properties:
- compatible : "cirrus,msm-cirrus-playback"
- usecase-names : List of strings representing the various usecases
available on a given platform (max of 4).
- ff-port-id : Port index of feedforward speaker enhancement module
Example:
msm-cirrus-playback {
compatible = "cirrus,msm-cirrus-playback";
usecase-names = "Playback", "Voice", "Headset", "Handsfree";
ff-port-id = "16384";
};

View file

@ -0,0 +1,118 @@
/* Copyright (c) 2015 Cirrus Logic, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef MSM_CIRRUS_PLAYBACK_H
#define MSM_CIRRUS_PLAYBACK_H
#include <linux/slab.h>
#include <sound/soc.h>
#include <dsp/apr_audio-v2.h>
#include <dsp/q6afe-v2.h>
#include <dsp/q6audio-v2.h>
#include <uapi/sound/cirrus-playback.h>
struct afe_custom_crus_set_config_t {
struct apr_hdr hdr;
struct afe_port_cmd_set_param_v2 param;
struct afe_port_param_data_v2 data;
} __packed;
struct afe_custom_crus_get_config_t {
struct apr_hdr hdr;
struct afe_port_cmd_get_param_v2 param;
struct afe_port_param_data_v2 data;
} __packed;
/* Payload struct for getting or setting one integer value from/to the DSP
* module
*/
struct crus_single_data_t {
int32_t value;
};
/* Payload struct for getting or setting two integer values from/to the DSP
* module
*/
struct crus_dual_data_t {
int32_t data1;
int32_t data2;
};
/* Payload struct for getting or setting three integer values from/to the DSP
* module
*/
struct crus_triple_data_t {
int32_t data1;
int32_t data2;
int32_t data3;
};
/* Payload struct for setting the RX and TX use cases */
struct crus_rx_run_case_ctrl_t {
int32_t value;
int32_t status_l;
int32_t checksum_l;
int32_t z_l;
int32_t status_r;
int32_t checksum_r;
int32_t z_r;
int32_t atemp;
};
/* Payload struct for getting calibration result from DSP module */
struct cirrus_cal_result_t {
int32_t status_l;
int32_t checksum_l;
int32_t z_l;
int32_t status_r;
int32_t checksum_r;
int32_t z_r;
};
#define APR_CHUNK_SIZE 256
#define CONFIG_FILE_SIZE 128
#define PAYLOAD_FOLLOWS_CONFIG 4
#define MAX_TUNING_CONFIGS 4
#define MIN_CHAN_SWAP_SAMPLES 48
#define MAX_CHAN_SWAP_SAMPLES 9600
/* Payload struct for sending an external configuration string to the DSP
* module
*/
struct crus_external_config_t {
uint32_t total_size;
uint32_t chunk_size;
int32_t done;
int32_t reserved;
int32_t config;
char data[APR_CHUNK_SIZE];
};
/* Payload struct for sending an external tuning transition string to the DSP
* module
*/
struct crus_delta_config_t {
uint32_t total_size;
uint32_t chunk_size;
int32_t done;
int32_t index;
int32_t reserved;
int32_t config;
char data[APR_CHUNK_SIZE];
};
extern int afe_apr_send_pkt_crus(void *data, int index);
extern int crus_afe_set_callback(
int32_t (*crus_afe_callback_func)(void *payload, int size));
void msm_crus_pb_add_controls(struct snd_soc_platform *platform);
#endif /* _MSM_CIRRUS_PLAYBACK_H */

View file

@ -54,6 +54,12 @@ LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
include $(DLKM_DIR)/AndroidKernelModule.mk
include $(CLEAR_VARS)
LOCAL_MODULE := cirrus_playback.ko
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
include $(DLKM_DIR)/AndroidKernelModule.mk
include $(CLEAR_VARS)
LOCAL_MODULE := snd_soc_tfa9874.ko
LOCAL_MODULE_TAGS := optional

View file

@ -59,6 +59,10 @@ ifneq ($(filter m y,$(CONFIG_SND_SOC_CS35L41_SPI)),)
else
cirrus_cs35l41-objs += cs35l41-i2c.o
endif
cirrus_playback-objs = msm-cirrus-playback.o
obj-m += cirrus_playback.o
snd_soc_tfa9874-objs += tfa98xx.o
snd_soc_tfa9874-objs += tfa_container.o
snd_soc_tfa9874-objs += tfa_dsp.o

View file

@ -0,0 +1,973 @@
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/firmware.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/compat.h>
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <sound/msm-cirrus-playback.h>
#define CRUS_RX_CONFIG "crus_sp_rx%d.bin"
static struct device *crus_se_device;
static atomic_t crus_se_misc_usage_count;
static struct crus_single_data_t crus_enable;
static struct crus_se_ioctl_header crus_se_hdr;
static uint32_t *crus_se_get_buffer;
static atomic_t crus_se_get_param_flag;
struct mutex crus_se_get_param_lock;
struct mutex crus_se_lock;
static int cirrus_se_en;
static int cirrus_se_usecase;
static int cirrus_ff_load_conf_sel;
static int cirrus_ff_delta_sel;
static int cirrus_ff_chan_swap_sel;
static int cirrus_ff_chan_swap_dur;
static int cirrus_ff_port = AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX;
static int crus_se_usecase_dt_count;
static const char *crus_se_usecase_dt_text[MAX_TUNING_CONFIGS];
int crus_afe_callback(void *payload, int size);
static void *crus_gen_afe_get_header(int length, int port, int module,
int param)
{
struct afe_custom_crus_get_config_t *config = NULL;
int size = sizeof(struct afe_custom_crus_get_config_t);
int index = afe_get_port_index(port);
uint16_t payload_size = sizeof(struct afe_port_param_data_v2) +
length;
/* Allocate memory for the message */
config = kzalloc(size, GFP_KERNEL);
if (!config)
return NULL;
/* Set header section */
config->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
config->hdr.pkt_size = size;
config->hdr.src_svc = APR_SVC_AFE;
config->hdr.src_domain = APR_DOMAIN_APPS;
config->hdr.src_port = 0;
config->hdr.dest_svc = APR_SVC_AFE;
config->hdr.dest_domain = APR_DOMAIN_ADSP;
config->hdr.dest_port = 0;
config->hdr.token = index;
config->hdr.opcode = AFE_PORT_CMD_GET_PARAM_V2;
/* Set param section */
config->param.port_id = (uint16_t) port;
config->param.payload_address_lsw = 0;
config->param.payload_address_msw = 0;
config->param.mem_map_handle = 0;
config->param.module_id = (uint32_t) module;
config->param.param_id = (uint32_t) param;
/* max data size of the param_ID/module_ID combination */
config->param.payload_size = payload_size;
/* Set data section */
config->data.module_id = (uint32_t) module;
config->data.param_id = (uint32_t) param;
config->data.reserved = 0; /* Must be set to 0 */
/* actual size of the data for the module_ID/param_ID pair */
config->data.param_size = length;
return (void *)config;
}
static void *crus_gen_afe_set_header(int length, int port, int module,
int param)
{
struct afe_custom_crus_set_config_t *config = NULL;
int size = sizeof(struct afe_custom_crus_set_config_t) + length;
int index = afe_get_port_index(port);
uint16_t payload_size = sizeof(struct afe_port_param_data_v2) +
length;
/* Allocate memory for the message */
config = kzalloc(size, GFP_KERNEL);
if (!config)
return NULL;
/* Set header section */
config->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
config->hdr.pkt_size = size;
config->hdr.src_svc = APR_SVC_AFE;
config->hdr.src_domain = APR_DOMAIN_APPS;
config->hdr.src_port = 0;
config->hdr.dest_svc = APR_SVC_AFE;
config->hdr.dest_domain = APR_DOMAIN_ADSP;
config->hdr.dest_port = 0;
config->hdr.token = index;
config->hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2;
/* Set param section */
config->param.port_id = (uint16_t) port;
config->param.payload_address_lsw = 0;
config->param.payload_address_msw = 0;
config->param.mem_map_handle = 0;
/* max data size of the param_ID/module_ID combination */
config->param.payload_size = payload_size;
/* Set data section */
config->data.module_id = (uint32_t) module;
config->data.param_id = (uint32_t) param;
config->data.reserved = 0; /* Must be set to 0 */
/* actual size of the data for the module_ID/param_ID pair */
config->data.param_size = length;
return (void *)config;
}
static int crus_get_param(int port, int module, int param, int length,
void *data)
{
struct afe_custom_crus_get_config_t *config = NULL;
int index = afe_get_port_index(port);
int ret = 0, count = 0;
pr_debug("%s: port = %d module = %d param = 0x%x length = %d\n",
__func__, port, module, param, length);
config = (struct afe_custom_crus_get_config_t *)
crus_gen_afe_get_header(length, port, module, param);
if (config == NULL) {
pr_err("%s: Memory allocation failed!\n", __func__);
return -ENOMEM;
}
pr_debug("%s: Preparing to send apr packet\n", __func__);
mutex_lock(&crus_se_get_param_lock);
atomic_set(&crus_se_get_param_flag, 0);
crus_se_get_buffer = kzalloc(length + 16, GFP_KERNEL);
if (!crus_se_get_buffer) {
pr_err("%s: Memory allocation failed!\n", __func__);
mutex_unlock(&crus_se_get_param_lock);
return -ENOMEM;
}
crus_afe_set_callback(crus_afe_callback);
ret = afe_apr_send_pkt_crus(config, index);
if (ret)
pr_err("%s: crus get_param for port %d failed with code %d\n",
__func__, port, ret);
else
pr_debug("%s: crus get_param sent packet with param id 0x%08x to module 0x%08x.\n",
__func__, param, module);
/* Wait for afe callback to populate data */
while (!atomic_read(&crus_se_get_param_flag)) {
usleep_range(800, 1200);
if (count++ >= 1000) {
pr_err("%s: AFE callback timeout\n", __func__);
atomic_set(&crus_se_get_param_flag, 1);
mutex_unlock(&crus_se_get_param_lock);
return -EINVAL;
}
}
/* Copy from dynamic buffer to return buffer */
memcpy((u8 *)data, &crus_se_get_buffer[4], length);
kfree(crus_se_get_buffer);
mutex_unlock(&crus_se_get_param_lock);
kfree(config);
return ret;
}
static int crus_set_param(int port, int module, int param, int length,
void *data_ptr)
{
struct afe_custom_crus_set_config_t *config = NULL;
int index = afe_get_port_index(port);
int ret = 0;
pr_debug("%s: port = %d module = %d param = 0x%x length = %d\n",
__func__, port, module, param, length);
config = crus_gen_afe_set_header(length, port, module, param);
if (config == NULL) {
pr_err("%s: Memory allocation failed!\n", __func__);
return -ENOMEM;
}
memcpy((u8 *)config + sizeof(struct afe_custom_crus_set_config_t),
(u8 *) data_ptr, length);
pr_debug("%s: Preparing to send apr packet.\n", __func__);
ret = afe_apr_send_pkt_crus(config, index);
if (ret) {
pr_err("%s: crus set_param for port %d failed with code %d\n",
__func__, port, ret);
} else {
pr_debug("%s: crus set_param sent packet with param id 0x%08x to module 0x%08x.\n",
__func__, param, module);
}
kfree(config);
return ret;
}
static int crus_send_config(const char *data, int32_t length,
int32_t port, int32_t module)
{
struct afe_custom_crus_set_config_t *config = NULL;
struct crus_external_config_t *payload = NULL;
int size = sizeof(struct crus_external_config_t);
int ret = 0;
int index = afe_get_port_index(port);
uint32_t param = 0;
int mem_size = 0;
int sent = 0;
int chars_to_send = 0;
pr_debug("%s: called with module_id = %x, string length = %d\n",
__func__, module, length);
/* Destination settings for message */
if (port == cirrus_ff_port)
param = CRUS_PARAM_RX_SET_EXT_CONFIG;
else {
pr_err("%s: Received invalid port parameter %d\n",
__func__, module);
return -EINVAL;
}
if (length > APR_CHUNK_SIZE)
mem_size = APR_CHUNK_SIZE;
else
mem_size = length;
config = crus_gen_afe_set_header(size, port, module, param);
if (config == NULL) {
pr_err("%s: Memory allocation failed!\n", __func__);
return -ENOMEM;
}
payload = (struct crus_external_config_t *)((u8 *)config +
sizeof(struct afe_custom_crus_set_config_t));
payload->total_size = (uint32_t)length;
payload->reserved = 0;
payload->config = PAYLOAD_FOLLOWS_CONFIG;
/* ^ This tells the algorithm to expect array */
/* immediately following the header */
/* Send config string in chunks of APR_CHUNK_SIZE bytes */
while (sent < length) {
chars_to_send = length - sent;
if (chars_to_send > APR_CHUNK_SIZE) {
chars_to_send = APR_CHUNK_SIZE;
payload->done = 0;
} else {
payload->done = 1;
}
/* Configure per message parameter settings */
memcpy(payload->data, data + sent, chars_to_send);
payload->chunk_size = chars_to_send;
/* Send the actual message */
pr_debug("%s: Preparing to send apr packet.\n", __func__);
ret = afe_apr_send_pkt_crus(config, index);
if (ret)
pr_err("%s: crus set_param for port %d failed with code %d\n",
__func__, port, ret);
else
pr_debug("%s: crus set_param sent packet with param id 0x%08x to module 0x%08x.\n",
__func__, param, module);
sent += chars_to_send;
}
kfree(config);
return ret;
}
static int crus_send_delta(const char *data, uint32_t length)
{
struct afe_custom_crus_set_config_t *config = NULL;
struct crus_delta_config_t *payload = NULL;
int size = sizeof(struct crus_delta_config_t);
int port = cirrus_ff_port;
int param = CRUS_PARAM_RX_SET_DELTA_CONFIG;
int module = CIRRUS_SE;
int ret = 0;
int index = afe_get_port_index(port);
int mem_size = 0;
int sent = 0;
int chars_to_send = 0;
pr_debug("%s: called with module_id = %x, string length = %d\n",
__func__, module, length);
if (length > APR_CHUNK_SIZE)
mem_size = APR_CHUNK_SIZE;
else
mem_size = length;
config = crus_gen_afe_set_header(size, port, module, param);
if (config == NULL) {
pr_err("%s: Memory allocation failed!\n", __func__);
return -ENOMEM;
}
payload = (struct crus_delta_config_t *)((u8 *)config +
sizeof(struct afe_custom_crus_set_config_t));
payload->total_size = length;
payload->index = 0;
payload->reserved = 0;
payload->config = PAYLOAD_FOLLOWS_CONFIG;
/* ^ This tells the algorithm to expect array */
/* immediately following the header */
/* Send config string in chunks of APR_CHUNK_SIZE bytes */
while (sent < length) {
chars_to_send = length - sent;
if (chars_to_send > APR_CHUNK_SIZE) {
chars_to_send = APR_CHUNK_SIZE;
payload->done = 0;
} else {
payload->done = 1;
}
/* Configure per message parameter settings */
memcpy(payload->data, data + sent, chars_to_send);
payload->chunk_size = chars_to_send;
/* Send the actual message */
pr_debug("%s: Preparing to send apr packet.\n", __func__);
ret = afe_apr_send_pkt_crus(config, index);
if (ret)
pr_err("%s: crus set_param for port %d failed with code %d\n",
__func__, port, ret);
else
pr_debug("%s: crus set_param sent packet with param id 0x%08x to module 0x%08x.\n",
__func__, param, module);
sent += chars_to_send;
}
kfree(config);
return ret;
}
int crus_afe_callback(void *payload, int size)
{
uint32_t *payload32 = payload;
pr_debug("Cirrus AFE CALLBACK: size = %d\n", size);
switch (payload32[1]) {
case CIRRUS_SE:
memcpy(crus_se_get_buffer, payload32, size);
atomic_set(&crus_se_get_param_flag, 1);
break;
default:
return -EINVAL;
}
return 0;
}
static int msm_crus_se_enable(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
static struct crus_single_data_t crus_id_ctrl;
const int crus_set = ucontrol->value.integer.value[0];
if (crus_set > 255) {
pr_err("Cirrus SE Enable: Invalid entry; Enter 0 to DISABLE, 1 to ENABLE\n");
return -EINVAL;
}
switch (crus_set) {
case 0: /* "Config SE Disable" */
pr_debug("Cirrus SE Enable: Config DISABLE\n");
crus_enable.value = 0;
cirrus_se_en = 0;
break;
case 1: /* "Config SE Enable" */
pr_debug("Cirrus SE Enable: Config ENABLE\n");
crus_enable.value = 1;
cirrus_se_en = 1;
break;
default:
return -EINVAL;
}
mutex_lock(&crus_se_lock);
crus_set_param(cirrus_ff_port, CIRRUS_SE,
CIRRUS_SE_ENABLE,
sizeof(struct crus_single_data_t),
(void *)&crus_enable);
crus_id_ctrl.value = 0; /*tx=1, rx=0*/
crus_set_param(cirrus_ff_port, CIRRUS_SE,
CIRRUS_SE_PARAM_ID_CTRL,
sizeof(struct crus_single_data_t),
(void *)&crus_id_ctrl);
mutex_unlock(&crus_se_lock);
return 0;
}
static int msm_crus_se_enable_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_debug("Starting Cirrus SE Enable Get function call\n");
crus_get_param(cirrus_ff_port, CIRRUS_SE, CIRRUS_SE_ENABLE,
sizeof(struct crus_single_data_t), (void *)&crus_enable);
ucontrol->value.integer.value[0] = crus_enable.value;
return 0;
}
static int msm_crus_se_usecase(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct crus_rx_run_case_ctrl_t case_ctrl;
const int crus_set = ucontrol->value.integer.value[0];
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
uint32_t max_index = e->items;
pr_debug("Starting Cirrus SE Config function call %d\n", crus_set);
if (crus_set >= max_index) {
pr_err("Cirrus SE Config index out of bounds (%d)\n", crus_set);
return -EINVAL;
}
cirrus_se_usecase = crus_set;
case_ctrl.status_l = 0;
case_ctrl.status_r = 0;
case_ctrl.atemp = 0;
case_ctrl.value = cirrus_se_usecase;
crus_set_param(cirrus_ff_port, CIRRUS_SE,
CRUS_PARAM_RX_SET_USECASE, sizeof(case_ctrl),
(void *)&case_ctrl);
return 0;
}
static int msm_crus_se_usecase_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
static struct crus_single_data_t crus_usecase;
pr_debug("Starting Cirrus SE Usecase Get function call\n");
crus_get_param(cirrus_ff_port, CIRRUS_SE, CRUS_PARAM_RX_GET_USECASE,
sizeof(struct crus_single_data_t), (void *)&crus_usecase);
ucontrol->value.integer.value[0] = crus_usecase.value;
return 0;
}
static int msm_crus_load_config(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
const int crus_set = ucontrol->value.integer.value[0];
char config[CONFIG_FILE_SIZE];
const struct firmware *firmware;
pr_debug("Starting Cirrus SE Load Config function call %d\n", crus_set);
switch (crus_set) {
case 0:
break;
case 1: /* Load RX Config */
cirrus_ff_load_conf_sel = crus_set;
snprintf(config, CONFIG_FILE_SIZE, CRUS_RX_CONFIG,
cirrus_se_usecase);
if (request_firmware(&firmware, config, crus_se_device) != 0) {
pr_err("%s: Request firmware failed\n", __func__);
return -EINVAL;
}
if (firmware->data != NULL && firmware->size > 0) {
pr_debug("%s: Sending RX config(%d)\n",
__func__, cirrus_se_usecase);
crus_send_config(firmware->data, firmware->size,
cirrus_ff_port, CIRRUS_SE);
} else {
pr_err("%s: can't read firmware data\n", __func__);
}
release_firmware(firmware);
break;
default:
return -EINVAL;
}
cirrus_ff_load_conf_sel = 0;
return 0;
}
static int msm_crus_load_config_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_debug("Starting Cirrus SE Load Config Get function call\n");
ucontrol->value.integer.value[0] = cirrus_ff_load_conf_sel;
return 0;
}
static int msm_crus_delta_config(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct crus_single_data_t data;
const int crus_set = ucontrol->value.integer.value[0];
const struct firmware *firmware;
pr_debug("Starting Cirrus SE Delta Config function call %d\n",
crus_set);
switch (crus_set) {
case 0:
break;
case 1: /* Load delta config over AFE */
cirrus_ff_delta_sel = crus_set;
if (request_firmware(&firmware, "crus_se_delta_config.bin",
crus_se_device) != 0) {
pr_err("%s: Request firmware failed\n", __func__);
cirrus_ff_delta_sel = 0;
return -EINVAL;
}
pr_debug("%s: Sending delta config\n", __func__);
crus_send_delta(firmware->data, firmware->size);
release_firmware(firmware);
break;
case 2: /* Run delta transition */
cirrus_ff_delta_sel = crus_set;
data.value = 0;
crus_set_param(cirrus_ff_port, CIRRUS_SE,
CRUS_PARAM_RX_RUN_DELTA_CONFIG,
sizeof(struct crus_single_data_t), &data);
break;
default:
return -EINVAL;
}
cirrus_ff_delta_sel = 0;
return 0;
}
static int msm_crus_delta_config_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_debug("Starting Cirrus SE Delta Config Get function call\n");
ucontrol->value.integer.value[0] = cirrus_ff_delta_sel;
return 0;
}
static int msm_crus_chan_swap(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct crus_dual_data_t data;
const int crus_set = ucontrol->value.integer.value[0];
pr_debug("Starting Cirrus SE Channel Swap function call %d\n",
crus_set);
switch (crus_set) {
case 0: /* L/R */
data.data1 = 1;
break;
case 1: /* R/L */
data.data1 = 2;
break;
default:
return -EINVAL;
}
data.data2 = cirrus_ff_chan_swap_dur;
crus_set_param(cirrus_ff_port, CIRRUS_SE,
CRUS_PARAM_RX_CHANNEL_SWAP,
sizeof(struct crus_dual_data_t), &data);
cirrus_ff_chan_swap_sel = crus_set;
return 0;
}
static int msm_crus_chan_swap_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_debug("Starting Cirrus SE Channel Swap Get function call\n");
ucontrol->value.integer.value[0] = cirrus_ff_chan_swap_sel;
return 0;
}
static int msm_crus_chan_swap_dur(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int crus_set = ucontrol->value.integer.value[0];
pr_debug("Starting Cirrus SE Channel Swap Duration function call\n");
if ((crus_set < 0) || (crus_set > MAX_CHAN_SWAP_SAMPLES)) {
pr_err("%s: Value out of range (%d)\n", __func__, crus_set);
return -EINVAL;
}
if (crus_set < MIN_CHAN_SWAP_SAMPLES) {
pr_debug("%s: Received %d, rounding up to min value %d\n",
__func__, crus_set, MIN_CHAN_SWAP_SAMPLES);
crus_set = MIN_CHAN_SWAP_SAMPLES;
}
cirrus_ff_chan_swap_dur = crus_set;
return 0;
}
static int msm_crus_chan_swap_dur_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
pr_debug("Starting Cirrus SE Channel Swap Duration Get function call\n");
ucontrol->value.integer.value[0] = cirrus_ff_chan_swap_dur;
return 0;
}
static const char * const crus_en_text[] = {"Disable", "Enable"};
static const char * const crus_conf_load_text[] = {"Idle", "Load RX"};
static const char * const crus_delta_text[] = {"Idle", "Load", "Run"};
static const char * const crus_chan_swap_text[] = {"LR", "RL"};
static const struct soc_enum crus_en_enum[] = {
SOC_ENUM_SINGLE_EXT(2, crus_en_text),
};
static struct soc_enum crus_se_usecase_enum[] = {
SOC_ENUM_SINGLE_EXT(MAX_TUNING_CONFIGS, crus_se_usecase_dt_text),
};
static const struct soc_enum crus_conf_load_enum[] = {
SOC_ENUM_SINGLE_EXT(2, crus_conf_load_text),
};
static const struct soc_enum crus_delta_enum[] = {
SOC_ENUM_SINGLE_EXT(3, crus_delta_text),
};
static const struct soc_enum crus_chan_swap_enum[] = {
SOC_ENUM_SINGLE_EXT(2, crus_chan_swap_text),
};
static const struct snd_kcontrol_new crus_mixer_controls[] = {
SOC_ENUM_EXT("Cirrus SE", crus_en_enum[0],
msm_crus_se_enable_get, msm_crus_se_enable),
SOC_ENUM_EXT("Cirrus SE Audio Mode", crus_se_usecase_enum[0],
msm_crus_se_usecase_get, msm_crus_se_usecase),
SOC_ENUM_EXT("Cirrus SE Load Config", crus_conf_load_enum[0],
msm_crus_load_config_get, msm_crus_load_config),
SOC_ENUM_EXT("Cirrus SE Delta Config", crus_delta_enum[0],
msm_crus_delta_config_get, msm_crus_delta_config),
SOC_ENUM_EXT("Cirrus SE Channel Swap", crus_chan_swap_enum[0],
msm_crus_chan_swap_get, msm_crus_chan_swap),
SOC_SINGLE_EXT("Cirrus SE Channel Swap Duration", SND_SOC_NOPM, 0,
MAX_CHAN_SWAP_SAMPLES, 0, msm_crus_chan_swap_dur_get,
msm_crus_chan_swap_dur),
};
void msm_crus_pb_add_controls(struct snd_soc_platform *platform)
{
crus_se_device = platform->dev;
if (crus_se_device == NULL)
pr_err("%s: platform->dev is NULL!\n", __func__);
else
pr_debug("%s: platform->dev = %lx\n", __func__,
(unsigned long)crus_se_device);
if (crus_se_usecase_dt_count == 0)
pr_err("%s: Missing usecase config selections\n", __func__);
crus_se_usecase_enum[0].items = crus_se_usecase_dt_count;
crus_se_usecase_enum[0].texts = crus_se_usecase_dt_text;
snd_soc_add_platform_controls(platform, crus_mixer_controls,
ARRAY_SIZE(crus_mixer_controls));
}
EXPORT_SYMBOL(msm_crus_pb_add_controls);
static long crus_se_shared_ioctl(struct file *f, unsigned int cmd,
void __user *arg)
{
int result = 0, port;
uint32_t bufsize = 0, size;
void *io_data = NULL;
pr_debug("%s\n", __func__);
if (copy_from_user(&size, arg, sizeof(size))) {
pr_err("%s: copy_from_user (size) failed\n", __func__);
result = -EFAULT;
goto exit;
}
/* Copy IOCTL header from usermode */
if (copy_from_user(&crus_se_hdr, arg, size)) {
pr_err("%s: copy_from_user (struct) failed\n", __func__);
result = -EFAULT;
goto exit;
}
bufsize = crus_se_hdr.data_length;
io_data = kzalloc(bufsize, GFP_KERNEL);
if (!io_data) {
pr_err("%s: Memory allocation failed!\n", __func__);
return -ENOMEM;
}
switch (cmd) {
case CRUS_SE_IOCTL_GET:
switch (crus_se_hdr.module_id) {
case CRUS_MODULE_ID_RX:
port = cirrus_ff_port;
break;
default:
pr_debug("%s: Unrecognized port ID (%d)\n", __func__,
crus_se_hdr.module_id);
port = cirrus_ff_port;
}
crus_get_param(port, CIRRUS_SE, crus_se_hdr.param_id,
bufsize, io_data);
result = copy_to_user(crus_se_hdr.data, io_data, bufsize);
if (result) {
pr_err("%s: copy_to_user failed (%d)\n", __func__,
result);
result = -EFAULT;
} else {
result = bufsize;
}
break;
case CRUS_SE_IOCTL_SET:
result = copy_from_user(io_data, (void *)crus_se_hdr.data,
bufsize);
if (result) {
pr_err("%s: copy_from_user failed (%d)\n", __func__,
result);
result = -EFAULT;
goto exit_io;
}
switch (crus_se_hdr.module_id) {
case CRUS_MODULE_ID_RX:
port = cirrus_ff_port;
break;
default:
pr_debug("%s: Unrecognized port ID (%d)\n", __func__,
crus_se_hdr.module_id);
port = cirrus_ff_port;
}
crus_set_param(port, CIRRUS_SE, crus_se_hdr.param_id,
bufsize, io_data);
break;
default:
pr_err("%s: Invalid IOCTL, command = %d!\n", __func__, cmd);
result = -EINVAL;
}
exit_io:
kfree(io_data);
exit:
return result;
}
static long crus_se_ioctl(struct file *f,
unsigned int cmd, unsigned long arg)
{
pr_debug("%s\n", __func__);
return crus_se_shared_ioctl(f, cmd, (void __user *)arg);
}
static long crus_se_compat_ioctl(struct file *f,
unsigned int cmd, unsigned long arg)
{
unsigned int cmd64;
pr_debug("%s\n", __func__);
switch (cmd) {
case CRUS_SE_IOCTL_GET32:
cmd64 = CRUS_SE_IOCTL_GET;
break;
case CRUS_SE_IOCTL_SET32:
cmd64 = CRUS_SE_IOCTL_SET;
break;
default:
pr_err("%s: Invalid IOCTL, command = %d!\n", __func__, cmd);
return -EINVAL;
}
return crus_se_shared_ioctl(f, cmd64, compat_ptr(arg));
}
static int crus_se_open(struct inode *inode, struct file *f)
{
int result = 0;
pr_debug("%s\n", __func__);
atomic_inc(&crus_se_misc_usage_count);
return result;
}
static int crus_se_release(struct inode *inode, struct file *f)
{
int result = 0;
pr_debug("%s\n", __func__);
atomic_dec(&crus_se_misc_usage_count);
pr_debug("%s: ref count %d!\n", __func__,
atomic_read(&crus_se_misc_usage_count));
return result;
}
static int msm_cirrus_playback_probe(struct platform_device *pdev)
{
int i, rc = 0;
u32 ff_port_id;
pr_info("CRUS_SE: initializing platform device\n");
crus_se_usecase_dt_count = of_property_count_strings(pdev->dev.of_node,
"usecase-names");
if (crus_se_usecase_dt_count <= 0) {
dev_dbg(&pdev->dev, "Usecase names not found\n");
crus_se_usecase_dt_count = 0;
return 0;
}
if ((crus_se_usecase_dt_count > 0) &&
(crus_se_usecase_dt_count <= MAX_TUNING_CONFIGS))
of_property_read_string_array(pdev->dev.of_node,
"usecase-names",
crus_se_usecase_dt_text,
crus_se_usecase_dt_count);
else if (crus_se_usecase_dt_count > MAX_TUNING_CONFIGS) {
dev_err(&pdev->dev, "Max of %d usecase configs allowed\n",
MAX_TUNING_CONFIGS);
return -EINVAL;
}
for (i = 0; i < crus_se_usecase_dt_count; i++)
pr_info("CRUS_SE: usecase[%d] = %s\n", i,
crus_se_usecase_dt_text[i]);
rc = of_property_read_u32_index(pdev->dev.of_node,
"afe-port-id", 0, &ff_port_id);
if (rc >= 0) {
cirrus_ff_port = ff_port_id;
pr_info("CRUS_SE: cirrus_ff_port = 0x%x", cirrus_ff_port);
}
return 0;
}
static const struct of_device_id msm_cirrus_playback_dt_match[] = {
{.compatible = "cirrus,msm-cirrus-playback"},
{}
};
MODULE_DEVICE_TABLE(of, msm_cirrus_playback_dt_match);
static struct platform_driver msm_cirrus_playback_driver = {
.driver = {
.name = "msm-cirrus-playback",
.owner = THIS_MODULE,
.of_match_table = msm_cirrus_playback_dt_match,
},
.probe = msm_cirrus_playback_probe,
};
static const struct file_operations crus_se_fops = {
.owner = THIS_MODULE,
.open = crus_se_open,
.release = crus_se_release,
.unlocked_ioctl = crus_se_ioctl,
.compat_ioctl = crus_se_compat_ioctl,
};
struct miscdevice crus_se_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "msm_cirrus_playback",
.fops = &crus_se_fops,
};
static int __init crus_se_init(void)
{
pr_info("CRUS_SE_INIT: initializing misc device\n");
atomic_set(&crus_se_get_param_flag, 0);
atomic_set(&crus_se_misc_usage_count, 0);
mutex_init(&crus_se_get_param_lock);
mutex_init(&crus_se_lock);
misc_register(&crus_se_misc);
return platform_driver_register(&msm_cirrus_playback_driver);
}
module_init(crus_se_init);
static void __exit crus_se_exit(void)
{
mutex_destroy(&crus_se_get_param_lock);
mutex_destroy(&crus_se_lock);
platform_driver_unregister(&msm_cirrus_playback_driver);
}
module_exit(crus_se_exit);
MODULE_DESCRIPTION("ASoC cirrus-playback driver");
MODULE_LICENSE("GPL");