| /* 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 <asoc/msm-cirrus-playback.h> |
| |
| #define CRUS_TX_CONFIG "crus_sp_config_%s_tx.bin" |
| #define CRUS_RX_CONFIG "crus_sp_config_%s_rx.bin" |
| |
| #define CRUS_TX_MULTI_CONFIG "crus_sp_config_%s_tx_%d.bin" |
| #define CRUS_RX_MULTI_CONFIG "crus_sp_config_%s_rx_%d.bin" |
| |
| #define CIRRUS_RX_TOPOLOGY 0x10000CCC |
| #define CIRRUS_TX_TOPOLOGY 0x10001CCC |
| |
| #define CRUS_PARAM_TEMP_MAX_LENGTH 384 |
| |
| static struct device *crus_sp_device; |
| static atomic_t crus_sp_misc_usage_count; |
| |
| static struct crus_single_data_t crus_enable; |
| |
| static struct crus_sp_ioctl_header crus_sp_hdr; |
| static struct cirrus_cal_result_t crus_sp_cal_rslt; |
| static int32_t *crus_sp_get_buffer; |
| static int32_t crus_sp_get_buffer_size; |
| static atomic_t crus_sp_get_param_flag; |
| struct mutex crus_sp_get_param_lock; |
| struct mutex crus_sp_lock; |
| static int cirrus_sp_en; |
| static int cirrus_sp_usecase; |
| static int cirrus_fb_port_ctl; |
| static int cirrus_fb_load_conf_sel; |
| static int cirrus_fb_delta_sel; |
| static int cirrus_ff_chan_swap_sel; |
| static int cirrus_ff_chan_swap_dur; |
| static bool cirrus_fail_detect_en; |
| static int cirrus_fb_port = AFE_PORT_ID_QUATERNARY_TDM_TX; |
| static int cirrus_ff_port = AFE_PORT_ID_QUATERNARY_TDM_RX; |
| |
| static int crus_sp_usecase_dt_count; |
| static const char *crus_sp_usecase_dt_text[MAX_TUNING_CONFIGS]; |
| |
| static bool swap_calibration; |
| static struct crus_cal_t crus_cal; |
| static struct cirrus_spk_component crus_spk; |
| static unsigned char count_config; |
| |
| static bool msm_crus_is_cirrus_afe_topology(void) |
| { |
| if (afe_get_topology(cirrus_ff_port) |
| == CIRRUS_RX_TOPOLOGY |
| && afe_get_topology(cirrus_fb_port) |
| == CIRRUS_TX_TOPOLOGY) |
| return true; |
| else |
| return false; |
| } |
| |
| 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) { |
| pr_err("%s: Unable to allocate memory for AFE message\n", |
| __func__); |
| 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) { |
| pr_err("%s: Unable to allocate memory for AFE message\n", |
| __func__); |
| 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_afe_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); |
| |
| if (!msm_crus_is_cirrus_afe_topology()) { |
| pr_warn("%s: afe port is not cirrus's topology", __func__); |
| return -EPERM; |
| } |
| |
| 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_sp_get_param_lock); |
| atomic_set(&crus_sp_get_param_flag, 0); |
| |
| crus_sp_get_buffer_size = config->param.payload_size + 16; |
| crus_sp_get_buffer = kzalloc(crus_sp_get_buffer_size, GFP_KERNEL); |
| |
| if (!crus_sp_get_buffer) { |
| pr_err("%s: kzalloc failed for crus_sp_get_buffer!\n", |
| __func__); |
| ret = -ENOMEM; |
| goto crus_sp_get_buffer_err; |
| } |
| |
| ret = afe_apr_send_pkt_crus(config, index, 0); |
| 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_sp_get_param_flag)) { |
| usleep_range(1000, 2000); |
| if (count++ >= 1000) { |
| pr_err("%s: AFE callback timeout\n", __func__); |
| atomic_set(&crus_sp_get_param_flag, 1); |
| ret = -EINVAL; |
| goto crus_sp_get_param_err; |
| } |
| } |
| |
| /* Copy from dynamic buffer to return buffer */ |
| memcpy((u8 *)data, &crus_sp_get_buffer[4], length); |
| |
| crus_sp_get_param_err: |
| kfree(crus_sp_get_buffer); |
| crus_sp_get_buffer = NULL; |
| crus_sp_get_buffer_size = -1; |
| |
| crus_sp_get_buffer_err: |
| mutex_unlock(&crus_sp_get_param_lock); |
| kfree(config); |
| return ret; |
| } |
| |
| static int crus_afe_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_info("%s: port = %d module = %d param = 0x%x length = %d\n", |
| __func__, port, module, param, length); |
| |
| if (!msm_crus_is_cirrus_afe_topology()) { |
| pr_warn("%s: afe port is not cirrus's topology", __func__); |
| return -EPERM; |
| } |
| |
| 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, 1); |
| 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_afe_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_info("%s: called with module_id = %x, string length = %d\n", |
| __func__, module, length); |
| |
| if (!msm_crus_is_cirrus_afe_topology()) { |
| pr_warn("%s: afe port is not cirrus's topology", __func__); |
| return -EPERM; |
| } |
| |
| /* Destination settings for message */ |
| if (port == cirrus_ff_port) |
| param = CRUS_PARAM_RX_SET_EXT_CONFIG; |
| else if (port == cirrus_fb_port) |
| param = CRUS_PARAM_TX_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, 1); |
| |
| 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_afe_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_SP; |
| int ret = 0; |
| int index = afe_get_port_index(port); |
| int mem_size = 0; |
| int sent = 0; |
| int chars_to_send = 0; |
| |
| pr_info("%s: called with module_id = %x, string length = %d\n", |
| __func__, module, length); |
| |
| if (!msm_crus_is_cirrus_afe_topology()) { |
| pr_warn("%s: afe port is not cirrus's topology", __func__); |
| return -EPERM; |
| } |
| |
| 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, 1); |
| |
| 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; |
| } |
| |
| extern int crus_afe_callback(void *payload, int size) |
| { |
| uint32_t *payload32 = payload; |
| int copysize; |
| |
| pr_debug("Cirrus AFE CALLBACK: size = %d\n", size); |
| if (size < 8) |
| return -EINVAL; |
| |
| switch (payload32[1]) { |
| case CIRRUS_SP: |
| if (crus_sp_get_buffer != NULL) { |
| copysize = (crus_sp_get_buffer_size > size) ? |
| size : crus_sp_get_buffer_size; |
| |
| if (copysize != size) |
| pr_warn("size mismatch data may lost\n"); |
| |
| memcpy(crus_sp_get_buffer, payload32, copysize); |
| atomic_set(&crus_sp_get_param_flag, 1); |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int msm_crus_send_usecase(int usecase) |
| { |
| struct crus_rx_run_case_ctrl_t case_ctrl; |
| |
| case_ctrl.status_l = 1; |
| case_ctrl.status_r = 1; |
| case_ctrl.z_l = crus_sp_cal_rslt.z_l; |
| case_ctrl.z_r = crus_sp_cal_rslt.z_r; |
| case_ctrl.checksum_l = crus_sp_cal_rslt.z_l + 1; |
| case_ctrl.checksum_r = crus_sp_cal_rslt.z_r + 1; |
| |
| case_ctrl.atemp = 23; |
| case_ctrl.value = usecase; |
| |
| if (crus_afe_set_param(cirrus_fb_port, CIRRUS_SP, |
| CRUS_PARAM_TX_SET_USECASE, sizeof(usecase), |
| (void *)&usecase)) |
| return -EPERM; |
| |
| if (crus_afe_set_param(cirrus_ff_port, CIRRUS_SP, |
| CRUS_PARAM_RX_SET_USECASE, sizeof(case_ctrl), |
| (void *)&case_ctrl)) |
| return -EPERM; |
| |
| return 0; |
| } |
| |
| static int msm_crus_send_config(int usecase, int port, |
| const struct firmware *firmware) |
| { |
| int ret = 0; |
| |
| if (firmware == NULL) |
| return -EINVAL; |
| |
| if (usecase >= crus_sp_usecase_dt_count) |
| return -EINVAL; |
| |
| if (port == SPK_RX) { |
| if (count_config & (1 << (SPK_RX + usecase * 2))) |
| return 0; |
| |
| |
| if (msm_crus_send_usecase(usecase)) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| if (crus_afe_send_config( |
| firmware->data, |
| firmware->size, |
| cirrus_ff_port, CIRRUS_SP)) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| count_config |= (1 << (SPK_RX + usecase * 2)); |
| } else if (port == SPK_TX) { |
| if (count_config & (1 << (SPK_TX + usecase * 2))) |
| return 0; |
| |
| if (msm_crus_send_usecase(usecase)) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| if (crus_afe_send_config( |
| firmware->data, |
| firmware->size, |
| cirrus_fb_port, CIRRUS_SP)) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| count_config |= (1 << (SPK_TX + usecase * 2)); |
| } else { |
| pr_err("%s: unknown port %d", __func__, port); |
| ret = -EINVAL; |
| } |
| |
| exit: |
| msm_crus_send_usecase(cirrus_sp_usecase); |
| return ret; |
| } |
| int msm_routing_cirrus_fbport_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| pr_debug("%s: cirrus_fb_port_ctl = %d", __func__, cirrus_fb_port_ctl); |
| ucontrol->value.integer.value[0] = cirrus_fb_port_ctl; |
| return 0; |
| } |
| |
| int msm_routing_cirrus_fbport_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| cirrus_fb_port_ctl = ucontrol->value.integer.value[0]; |
| |
| switch (cirrus_fb_port_ctl) { |
| case 0: |
| cirrus_fb_port = AFE_PORT_ID_PRIMARY_MI2S_TX; |
| cirrus_ff_port = AFE_PORT_ID_PRIMARY_MI2S_RX; |
| break; |
| case 1: |
| cirrus_fb_port = AFE_PORT_ID_SECONDARY_MI2S_TX; |
| cirrus_ff_port = AFE_PORT_ID_SECONDARY_MI2S_RX; |
| break; |
| case 2: |
| cirrus_fb_port = AFE_PORT_ID_TERTIARY_MI2S_TX; |
| cirrus_ff_port = AFE_PORT_ID_TERTIARY_MI2S_RX; |
| break; |
| case 3: |
| cirrus_fb_port = AFE_PORT_ID_QUATERNARY_MI2S_TX; |
| cirrus_ff_port = AFE_PORT_ID_QUATERNARY_MI2S_RX; |
| break; |
| case 4: |
| cirrus_fb_port = AFE_PORT_ID_QUATERNARY_TDM_TX; |
| cirrus_ff_port = AFE_PORT_ID_QUATERNARY_TDM_RX; |
| break; |
| case 5: |
| cirrus_fb_port = AFE_PORT_ID_SECONDARY_TDM_TX; |
| cirrus_ff_port = AFE_PORT_ID_SECONDARY_TDM_RX; |
| break; |
| default: |
| /* Default port to QUATERNARY */ |
| cirrus_fb_port_ctl = 4; |
| cirrus_fb_port = AFE_PORT_ID_QUATERNARY_TDM_TX; |
| cirrus_ff_port = AFE_PORT_ID_QUATERNARY_TDM_RX; |
| break; |
| } |
| return 0; |
| } |
| |
| static int msm_routing_crus_sp_enable(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| const int crus_set = ucontrol->value.integer.value[0]; |
| int ret = 0; |
| |
| if (crus_set > 255) { |
| pr_err("Cirrus SP Enable: Invalid entry; Enter 0 to DISABLE," |
| " 1 to ENABLE; 2-255 are reserved for debug\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&crus_sp_lock); |
| |
| switch (crus_set) { |
| case 0: /* "Config SP Disable" */ |
| pr_info("Cirrus SP Enable: Config DISABLE\n"); |
| crus_enable.value = 0; |
| cirrus_sp_en = 0; |
| break; |
| case 1: /* "Config SP Enable" */ |
| pr_info("Cirrus SP Enable: Config ENABLE\n"); |
| crus_enable.value = 1; |
| cirrus_sp_en = 1; |
| break; |
| default: |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| crus_spk.flag = crus_spk.flag & ~CIRRUS_ENABLE; |
| |
| if (crus_afe_set_param(cirrus_ff_port, CIRRUS_SP, |
| CIRRUS_SP_ENABLE, |
| sizeof(struct crus_single_data_t), |
| (void *)&crus_enable)) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| if (crus_afe_set_param(cirrus_fb_port, CIRRUS_SP, |
| CIRRUS_SP_ENABLE, |
| sizeof(struct crus_single_data_t), |
| (void *)&crus_enable)) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| crus_spk.flag = crus_spk.flag | CIRRUS_ENABLE; |
| |
| exit: |
| mutex_unlock(&crus_sp_lock); |
| pr_debug("%s: flag: %x ret: %d", __func__, |
| crus_spk.flag, ret); |
| |
| return ret; |
| } |
| |
| static int msm_routing_crus_sp_enable_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| pr_info("Starting Cirrus SP Enable Get function call : %d\n", |
| cirrus_sp_en); |
| ucontrol->value.integer.value[0] = cirrus_sp_en; |
| return 0; |
| } |
| |
| static bool msm_crus_check_count(unsigned char number, int total_count) |
| { |
| int i = 0; |
| |
| pr_debug("%s: number: %x, total_count: %d", __func__, |
| number, total_count); |
| |
| for (i = 0; i < total_count; i++) { |
| if (!(number & (1 << i))) |
| return false; |
| } |
| return true; |
| } |
| |
| static int msm_routing_crus_sp_usecase(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| 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; |
| int ret = 0; |
| |
| pr_debug("Starting Cirrus SP Config function call %d\n", crus_set); |
| |
| if (crus_set >= max_index) { |
| pr_err("Cirrus SP Config index out of bounds (%d)\n", crus_set); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&crus_sp_lock); |
| |
| cirrus_sp_usecase = crus_set; |
| crus_spk.flag = crus_spk.flag & ~CIRRUS_USECASE; |
| |
| if (msm_crus_send_usecase(cirrus_sp_usecase)) { |
| ret = -EPERM; |
| goto exit; |
| } |
| |
| crus_spk.flag = crus_spk.flag | CIRRUS_USECASE; |
| |
| exit: |
| mutex_unlock(&crus_sp_lock); |
| pr_debug("%s: flag: %x ret: %d", __func__, |
| crus_spk.flag, ret); |
| |
| return ret; |
| } |
| |
| static int msm_routing_crus_sp_usecase_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| pr_debug("Starting Cirrus SP Config Get function call\n"); |
| ucontrol->value.integer.value[0] = cirrus_sp_usecase; |
| |
| return 0; |
| } |
| |
| static int msm_routing_crus_load_config(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| const int crus_set = ucontrol->value.integer.value[0]; |
| char *config = NULL; |
| const struct firmware *firmware = NULL; |
| struct snd_soc_platform *platform = snd_soc_kcontrol_platform(kcontrol); |
| struct msm_pcm_drv_data *pdata = snd_soc_platform_get_drvdata(platform); |
| int rc = 0; |
| int i = 0; |
| |
| mutex_lock(&crus_sp_lock); |
| |
| config = kzalloc(CONFIG_FILE_SIZE, GFP_KERNEL); |
| if (!config) { |
| rc = -ENOMEM; |
| goto exit; |
| } |
| |
| pr_debug("Starting Cirrus SP Load Config function call %d\n", crus_set); |
| |
| switch (crus_set) { |
| case 0: |
| break; |
| case 1: /* Load RX Config */ |
| cirrus_fb_load_conf_sel = crus_set; |
| |
| for (i = 0; i < crus_sp_usecase_dt_count; i++) { |
| if (crus_spk.bin_files[SPK_RX][i]) { |
| pr_debug("%s: stored RX firmware usecase: %d already", |
| __func__, i); |
| continue; |
| } |
| |
| if (i == 0) |
| snprintf(config, CONFIG_FILE_SIZE, |
| CRUS_RX_CONFIG, |
| pdata->config_name); |
| else |
| snprintf(config, CONFIG_FILE_SIZE, |
| CRUS_RX_MULTI_CONFIG, |
| pdata->config_name, i); |
| |
| if (request_firmware(&firmware, config, |
| crus_sp_device) != 0) { |
| pr_err("%s: Request firmware %s failed\n", |
| __func__, config); |
| continue; |
| } else { |
| pr_info("%s: Sending RX config\n", __func__); |
| crus_spk.bin_files[SPK_RX][i] = firmware; |
| |
| if (msm_crus_send_config(i, SPK_RX, |
| crus_spk.bin_files[SPK_RX][i])) |
| continue; |
| } |
| } |
| break; |
| case 2: /* Load TX Config */ |
| cirrus_fb_load_conf_sel = crus_set; |
| |
| for (i = 0; i < crus_sp_usecase_dt_count; i++) { |
| if (crus_spk.bin_files[SPK_TX][i]) { |
| pr_debug("%s: stored TX firmware usecase: %d already", |
| __func__, i); |
| continue; |
| } |
| |
| if (i == 0) |
| snprintf(config, CONFIG_FILE_SIZE, |
| CRUS_TX_CONFIG, |
| pdata->config_name); |
| else |
| snprintf(config, CONFIG_FILE_SIZE, |
| CRUS_TX_MULTI_CONFIG, |
| pdata->config_name, i); |
| |
| if (request_firmware(&firmware, config, |
| crus_sp_device) != 0) { |
| pr_err("%s: Request firmware %s failed\n", |
| __func__, config); |
| continue; |
| } else { |
| pr_info("%s: Sending TX config\n", __func__); |
| crus_spk.bin_files[SPK_TX][i] = firmware; |
| |
| if (msm_crus_send_config(i, SPK_TX, |
| crus_spk.bin_files[SPK_TX][i])) |
| continue; |
| } |
| } |
| break; |
| default: |
| rc = -EINVAL; |
| goto exit; |
| } |
| |
| if (msm_crus_check_count(count_config, |
| MAX_SPK_PORT * crus_sp_usecase_dt_count)) { |
| count_config = 0; |
| crus_spk.flag = crus_spk.flag | CIRRUS_BIN_FILE; |
| } |
| exit: |
| cirrus_fb_load_conf_sel = 0; |
| kfree(config); |
| mutex_unlock(&crus_sp_lock); |
| pr_debug("%s: flag: %x rc: %d", __func__, |
| crus_spk.flag, rc); |
| return rc; |
| } |
| |
| static int msm_routing_crus_load_config_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| pr_debug("Starting Cirrus SP Load Config Get function call\n"); |
| ucontrol->value.integer.value[0] = cirrus_fb_load_conf_sel; |
| |
| return 0; |
| } |
| |
| static int msm_routing_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 SP Delta Config function call %d\n", |
| crus_set); |
| |
| switch (crus_set) { |
| case 0: |
| break; |
| case 1: /* Load delta config over AFE */ |
| cirrus_fb_delta_sel = crus_set; |
| if (request_firmware(&firmware, "crus_sp_delta_config.bin", |
| crus_sp_device) != 0) { |
| pr_err("%s: Request firmware failed\n", __func__); |
| cirrus_fb_delta_sel = 0; |
| return -EINVAL; |
| } else { |
| pr_info("%s: Sending delta config\n", __func__); |
| crus_afe_send_delta(firmware->data, firmware->size); |
| release_firmware(firmware); |
| } |
| break; |
| case 2: /* Run delta transition */ |
| cirrus_fb_delta_sel = crus_set; |
| data.value = 0; |
| crus_afe_set_param(cirrus_ff_port, CIRRUS_SP, |
| CRUS_PARAM_RX_RUN_DELTA_CONFIG, |
| sizeof(struct crus_single_data_t), &data); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| cirrus_fb_delta_sel = 0; |
| return 0; |
| } |
| |
| static int msm_routing_crus_delta_config_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| pr_debug("Starting Cirrus SP Delta Config Get function call\n"); |
| ucontrol->value.integer.value[0] = cirrus_fb_delta_sel; |
| |
| return 0; |
| } |
| |
| static int msm_routing_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 SP 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_afe_set_param(cirrus_ff_port, CIRRUS_SP, |
| CRUS_PARAM_RX_CHANNEL_SWAP, |
| sizeof(struct crus_dual_data_t), &data); |
| |
| cirrus_ff_chan_swap_sel = crus_set; |
| |
| return 0; |
| } |
| |
| static int msm_routing_crus_chan_swap_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| pr_debug("Starting Cirrus SP Channel Swap Get function call\n"); |
| ucontrol->value.integer.value[0] = cirrus_ff_chan_swap_sel; |
| |
| return 0; |
| } |
| |
| static int msm_routing_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 SP 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_info("%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_routing_crus_chan_swap_dur_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| pr_debug("Starting Cirrus SP Channel Swap Duration Get function call\n"); |
| ucontrol->value.integer.value[0] = cirrus_ff_chan_swap_dur; |
| |
| return 0; |
| } |
| |
| static int msm_routing_crus_fail_det(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| cirrus_fail_detect_en = ucontrol->value.integer.value[0]; |
| return 0; |
| } |
| |
| static int msm_routing_crus_fail_det_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.integer.value[0] = cirrus_fail_detect_en; |
| return 0; |
| } |
| |
| static const char *cirrus_fb_port_text[] = {"PRI_MI2S_RX", "SEC_MI2S_RX", |
| "TERT_MI2S_RX", "QUAT_MI2S_RX", |
| "QUAT_TDM_RX_0", "SEC_TDM_RX_0"}; |
| |
| static const char *crus_en_text[] = {"Config SP Disable", "Config SP Enable"}; |
| |
| static const char *crus_conf_load_text[] = {"Idle", "Load RX", "Load TX"}; |
| |
| static const char *crus_delta_text[] = {"Idle", "Load", "Run"}; |
| |
| static const char *crus_chan_swap_text[] = {"LR", "RL"}; |
| |
| static const struct soc_enum cirrus_fb_controls_enum[] = { |
| SOC_ENUM_SINGLE_EXT(6, cirrus_fb_port_text), |
| }; |
| |
| static const struct soc_enum crus_en_enum[] = { |
| SOC_ENUM_SINGLE_EXT(2, crus_en_text), |
| }; |
| |
| static struct soc_enum crus_sp_usecase_enum[] = { |
| SOC_ENUM_SINGLE_EXT(MAX_TUNING_CONFIGS, crus_sp_usecase_dt_text), |
| }; |
| |
| static const struct soc_enum crus_conf_load_enum[] = { |
| SOC_ENUM_SINGLE_EXT(3, 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 SP FBPort", cirrus_fb_controls_enum[0], |
| msm_routing_cirrus_fbport_get, msm_routing_cirrus_fbport_put), |
| SOC_ENUM_EXT("Cirrus SP", crus_en_enum[0], |
| msm_routing_crus_sp_enable_get, msm_routing_crus_sp_enable), |
| SOC_ENUM_EXT("Cirrus SP Usecase", crus_sp_usecase_enum[0], |
| msm_routing_crus_sp_usecase_get, msm_routing_crus_sp_usecase), |
| SOC_ENUM_EXT("Cirrus SP Load Config", crus_conf_load_enum[0], |
| msm_routing_crus_load_config_get, msm_routing_crus_load_config), |
| SOC_ENUM_EXT("Cirrus SP Delta Config", crus_delta_enum[0], |
| msm_routing_crus_delta_config_get, msm_routing_crus_delta_config), |
| SOC_ENUM_EXT("Cirrus SP Channel Swap", crus_chan_swap_enum[0], |
| msm_routing_crus_chan_swap_get, msm_routing_crus_chan_swap), |
| SOC_SINGLE_EXT("Cirrus SP Channel Swap Duration", SND_SOC_NOPM, 0, |
| MAX_CHAN_SWAP_SAMPLES, 0, msm_routing_crus_chan_swap_dur_get, |
| msm_routing_crus_chan_swap_dur), |
| SOC_SINGLE_BOOL_EXT("Cirrus SP Failure Detection", 0, |
| msm_routing_crus_fail_det_get, msm_routing_crus_fail_det), |
| }; |
| |
| void msm_crus_pb_add_controls(struct snd_soc_platform *platform) |
| { |
| crus_sp_device = platform->dev; |
| |
| if (crus_sp_device == NULL) |
| pr_err("%s: platform->dev is NULL!\n", __func__); |
| else |
| pr_debug("%s: platform->dev = %lx\n", __func__, |
| (unsigned long)crus_sp_device); |
| |
| if (crus_sp_usecase_dt_count == 0) { |
| pr_err("%s: Missing usecase config selections\n", __func__); |
| } |
| |
| crus_sp_usecase_enum[0].items = crus_sp_usecase_dt_count; |
| crus_sp_usecase_enum[0].texts = crus_sp_usecase_dt_text; |
| |
| snd_soc_add_platform_controls(platform, crus_mixer_controls, |
| ARRAY_SIZE(crus_mixer_controls)); |
| } |
| |
| static u32 get_bound_z(u32 z, u32 tolerance, char bound) |
| { |
| if (bound == 'u') |
| return z + z * tolerance / 100; |
| else |
| return z - z * tolerance / 100; |
| } |
| |
| static void msm_crus_check_calibration_value(void) |
| { |
| int32_t swap = 0; |
| u32 left_lower_bound = 0, left_upper_bound = 0; |
| u32 right_lower_bound = 0, right_upper_bound = 0; |
| |
| if (swap_calibration) { |
| pr_info("%s: swap calibration value", __func__); |
| swap = crus_sp_cal_rslt.status_l; |
| crus_sp_cal_rslt.status_l = crus_sp_cal_rslt.status_r; |
| crus_sp_cal_rslt.status_r = swap; |
| |
| swap = crus_sp_cal_rslt.checksum_l; |
| crus_sp_cal_rslt.checksum_l = crus_sp_cal_rslt.checksum_r; |
| crus_sp_cal_rslt.checksum_r = swap; |
| |
| swap = crus_sp_cal_rslt.z_l; |
| crus_sp_cal_rslt.z_l = crus_sp_cal_rslt.z_r; |
| crus_sp_cal_rslt.z_r = swap; |
| } |
| |
| left_lower_bound = get_bound_z(crus_cal.top_spk_impedance, |
| crus_cal.top_spk_tolerance, 'l'); |
| left_upper_bound = get_bound_z(crus_cal.top_spk_impedance, |
| crus_cal.top_spk_tolerance, 'u'); |
| |
| right_lower_bound = get_bound_z(crus_cal.bottom_spk_impedance, |
| crus_cal.bottom_spk_tolerance, 'l'); |
| right_upper_bound = get_bound_z(crus_cal.bottom_spk_impedance, |
| crus_cal.bottom_spk_tolerance, 'u'); |
| |
| pr_debug("%s: left bound: Lower: %u Upper: %u , right bound Lower: %u Upper: %u", __func__, |
| left_lower_bound, left_upper_bound, right_lower_bound, right_upper_bound); |
| |
| if (crus_sp_cal_rslt.z_l > left_upper_bound || crus_sp_cal_rslt.z_l < left_lower_bound) { |
| pr_err("%s: left calibartion %u over limit, set default value %u", __func__, |
| crus_sp_cal_rslt.z_l, crus_cal.top_spk_mean); |
| crus_sp_cal_rslt.z_l = crus_cal.top_spk_mean; |
| } |
| |
| if (crus_sp_cal_rslt.z_r > right_upper_bound || crus_sp_cal_rslt.z_r < right_lower_bound) { |
| pr_err("%s: right calibartion %u over limit, set default value %u", __func__, |
| crus_sp_cal_rslt.z_r, crus_cal.bottom_spk_mean); |
| crus_sp_cal_rslt.z_r = crus_cal.bottom_spk_mean; |
| } |
| } |
| |
| int msm_crus_store_imped(char channel) |
| { |
| /* cs35l36 speaker amp constant value */ |
| const int scale_factor = 100000000; |
| const int amp_factor = 71498; |
| int32_t buffer[96] = {0}; |
| int out_cal0; |
| int out_cal1; |
| |
| if (!msm_crus_is_cirrus_afe_topology()) |
| return -EINVAL; |
| |
| if (crus_afe_get_param(cirrus_ff_port, |
| CIRRUS_SP, CRUS_PARAM_RX_GET_TEMP, sizeof(buffer), buffer)) |
| return -EINVAL; |
| |
| if (channel == 'l') { |
| out_cal0 = buffer[12]; |
| out_cal1 = buffer[13]; |
| |
| if ((out_cal0 != 2) || (out_cal1 != 2)) |
| return -EINVAL; |
| |
| crus_spk.imp_l = buffer[3] * amp_factor; |
| |
| pr_info("%s: left impedance %d.%d ohms", __func__, |
| crus_spk.imp_l / scale_factor, |
| crus_spk.imp_l % scale_factor); |
| |
| } else if (channel == 'r') { |
| out_cal0 = buffer[14]; |
| out_cal1 = buffer[15]; |
| |
| if ((out_cal0 != 2) || (out_cal1 != 2)) |
| return -EINVAL; |
| |
| crus_spk.imp_r = buffer[1] * amp_factor; |
| |
| pr_info("%s: right impedance %d.%d ohms", __func__, |
| crus_spk.imp_r / scale_factor, |
| crus_spk.imp_r % scale_factor); |
| |
| } else |
| pr_err("%s: unknown channel", __func__); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(msm_crus_store_imped); |
| |
| static ssize_t |
| resistance_left_right_show(struct device *dev, |
| struct device_attribute *a, char *buf) |
| { |
| const int scale_factor = 100000000; |
| |
| return snprintf(buf, PAGE_SIZE, "%d.%d,%d.%d", |
| crus_spk.imp_l / scale_factor, |
| crus_spk.imp_l % scale_factor, |
| crus_spk.imp_r / scale_factor, |
| crus_spk.imp_r % scale_factor); |
| } |
| |
| static DEVICE_ATTR_RO(resistance_left_right); |
| |
| void msm_crus_check_set_setting(unsigned char cmd) |
| { |
| int i = 0, retries = 10; |
| |
| pr_debug("%s: cmd: %d", __func__, cmd); |
| mutex_lock(&crus_sp_lock); |
| |
| if (cmd == CS35L36_UNMUTE) { |
| if (crus_spk.flag == CIRRUS_ALL_SETTING) { |
| pr_debug("%s: setting is ready", __func__); |
| goto exit; |
| } |
| |
| do { |
| if (!(crus_spk.flag & CIRRUS_ENABLE)) { |
| if (crus_afe_set_param(cirrus_ff_port, |
| CIRRUS_SP, |
| CIRRUS_SP_ENABLE, |
| sizeof(struct crus_single_data_t), |
| (void *)&crus_enable)) |
| continue; |
| |
| if (crus_afe_set_param(cirrus_fb_port, |
| CIRRUS_SP, |
| CIRRUS_SP_ENABLE, |
| sizeof(struct crus_single_data_t), |
| (void *)&crus_enable)) |
| continue; |
| |
| crus_spk.flag = crus_spk.flag | |
| CIRRUS_ENABLE; |
| |
| pr_debug("%s: send enable: %d successfully", |
| __func__, crus_enable.value); |
| } |
| |
| if (!(crus_spk.flag & CIRRUS_USECASE)) { |
| if (msm_crus_send_usecase(cirrus_sp_usecase)) |
| continue; |
| |
| crus_spk.flag = crus_spk.flag | |
| CIRRUS_USECASE; |
| |
| pr_debug("%s: send USECASE: %d successfully", |
| __func__, cirrus_sp_usecase); |
| } |
| |
| if (!(crus_spk.flag & CIRRUS_BIN_FILE)) { |
| for (i = 0; i < crus_sp_usecase_dt_count; i++) { |
| if (!crus_spk.bin_files |
| [SPK_RX][i] || |
| !crus_spk.bin_files |
| [SPK_TX][i]) { |
| pr_info("%s: profile: %d is not ready", |
| __func__, i); |
| continue; |
| } |
| |
| if (msm_crus_send_config(i, SPK_TX, |
| crus_spk.bin_files[SPK_TX][i])) |
| continue; |
| |
| if (msm_crus_send_config(i, SPK_RX, |
| crus_spk.bin_files[SPK_RX][i])) |
| continue; |
| } |
| |
| if (msm_crus_check_count(count_config, |
| MAX_SPK_PORT * |
| crus_sp_usecase_dt_count)) { |
| count_config = 0; |
| crus_spk.flag = crus_spk.flag | |
| CIRRUS_BIN_FILE; |
| |
| pr_debug("%s: send bin files successfully", |
| __func__); |
| } |
| } |
| } while (crus_spk.flag != CIRRUS_ALL_SETTING && retries-- > 0); |
| } else if (cmd == AFE_SSR) { |
| pr_debug("%s: clear flag", __func__); |
| crus_spk.flag = 0; |
| } else { |
| pr_err("%s: unknown command: %d", __func__, cmd); |
| } |
| |
| if (retries <= 0 && crus_spk.flag != 0 && count_config != 0) { |
| pr_err("%s: stop to retry", __func__); |
| count_config = 0; |
| crus_spk.flag = CIRRUS_ALL_SETTING; |
| } |
| exit: |
| mutex_unlock(&crus_sp_lock); |
| pr_debug("%s: crus_spk.flag: %x", __func__, |
| crus_spk.flag); |
| } |
| EXPORT_SYMBOL(msm_crus_check_set_setting); |
| |
| static long crus_sp_shared_ioctl(struct file *f, unsigned int cmd, |
| void __user *arg) |
| { |
| int result = 0, port; |
| uint32_t bufsize = 0, size; |
| uint32_t option = 0; |
| void *io_data = NULL; |
| |
| pr_info("%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; |
| } |
| |
| if (size != sizeof(crus_sp_hdr)) { |
| pr_err("%s: the payload size is invalid", __func__); |
| result = -EINVAL; |
| goto exit; |
| } |
| |
| /* Copy IOCTL header from usermode */ |
| if (copy_from_user(&crus_sp_hdr, arg, size)) { |
| pr_err("%s: copy_from_user (struct) failed\n", __func__); |
| result = -EFAULT; |
| goto exit; |
| } |
| |
| if (crus_sp_hdr.data_length > CRUS_PARAM_TEMP_MAX_LENGTH) { |
| pr_err("data_length(%d) invalid\n", crus_sp_hdr.data_length); |
| result = -EINVAL; |
| goto exit; |
| } |
| |
| bufsize = crus_sp_hdr.data_length; |
| io_data = kzalloc(bufsize, GFP_KERNEL); |
| |
| switch (cmd) { |
| case CRUS_SP_IOCTL_GET: |
| switch (crus_sp_hdr.module_id) { |
| case CRUS_MODULE_ID_TX: |
| port = cirrus_fb_port; |
| break; |
| case CRUS_MODULE_ID_RX: |
| port = cirrus_ff_port; |
| break; |
| default: |
| pr_info("%s: Unrecognized port ID (%d)\n", __func__, |
| crus_sp_hdr.module_id); |
| port = cirrus_ff_port; |
| } |
| |
| crus_afe_get_param(port, CIRRUS_SP, crus_sp_hdr.param_id, |
| bufsize, io_data); |
| |
| result = copy_to_user(crus_sp_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_SP_IOCTL_SET: |
| result = copy_from_user(io_data, (void *)crus_sp_hdr.data, |
| bufsize); |
| if (result) { |
| pr_err("%s: copy_from_user failed (%d)\n", __func__, |
| result); |
| result = -EFAULT; |
| goto exit_io; |
| } |
| |
| switch (crus_sp_hdr.module_id) { |
| case CRUS_MODULE_ID_TX: |
| port = cirrus_fb_port; |
| break; |
| case CRUS_MODULE_ID_RX: |
| port = cirrus_ff_port; |
| break; |
| default: |
| pr_info("%s: Unrecognized port ID (%d)\n", __func__, |
| crus_sp_hdr.module_id); |
| port = cirrus_ff_port; |
| } |
| |
| crus_afe_set_param(port, CIRRUS_SP, crus_sp_hdr.param_id, |
| bufsize, io_data); |
| break; |
| case CRUS_SP_IOCTL_GET_CALIB: |
| if (copy_from_user(io_data, |
| (void *)crus_sp_hdr.data, bufsize)) { |
| pr_err("%s: copy_from_user failed\n", __func__); |
| result = -EFAULT; |
| goto exit_io; |
| } |
| option = 1; |
| crus_afe_set_param(cirrus_ff_port, CIRRUS_SP, |
| CRUS_PARAM_RX_SET_CALIB, sizeof(option), |
| (void *)&option); |
| crus_afe_set_param(cirrus_fb_port, CIRRUS_SP, |
| CRUS_PARAM_TX_SET_CALIB, sizeof(option), |
| (void *)&option); |
| msleep(2000); |
| crus_afe_get_param(cirrus_fb_port, CIRRUS_SP, |
| CRUS_PARAM_TX_GET_TEMP_CAL, bufsize, |
| io_data); |
| if (copy_to_user(crus_sp_hdr.data, io_data, bufsize)) { |
| pr_err("%s: copy_to_user failed\n", __func__); |
| result = -EFAULT; |
| } else { |
| result = bufsize; |
| } |
| |
| break; |
| case CRUS_SP_IOCTL_SET_CALIB: |
| if (bufsize != sizeof(crus_sp_cal_rslt)) { |
| pr_err("%s: the data size is invalid", __func__); |
| result = -EINVAL; |
| goto exit_io; |
| } |
| |
| if (copy_from_user(io_data, |
| (void *)crus_sp_hdr.data, bufsize)) { |
| pr_err("%s: copy_from_user failed\n", __func__); |
| result = -EFAULT; |
| goto exit_io; |
| } |
| |
| memcpy(&crus_sp_cal_rslt, io_data, bufsize); |
| |
| msm_crus_check_calibration_value(); |
| |
| 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_sp_ioctl(struct file *f, |
| unsigned int cmd, unsigned long arg) |
| { |
| pr_info("%s\n", __func__); |
| |
| return crus_sp_shared_ioctl(f, cmd, (void __user *)arg); |
| } |
| |
| struct compat_crus_sp_ioctl_header { |
| uint32_t size; |
| uint32_t module_id; |
| uint32_t param_id; |
| uint32_t data_length; |
| compat_caddr_t data; |
| }; |
| |
| static long crus_sp_compat_ioctl(struct file *f, |
| unsigned int cmd, unsigned long arg) |
| { |
| unsigned int cmd64; |
| struct compat_crus_sp_ioctl_header __user *ua32; |
| struct crus_sp_ioctl_header __user *ua; |
| compat_caddr_t __user ua32_data; |
| void __user *ua_data; |
| uint32_t ua32_size, ua_size; |
| |
| pr_info("%s\n", __func__); |
| |
| ua32 = compat_ptr(arg); |
| if (get_user(ua32_size, (uint32_t __user*)&ua32->size)) |
| return -EFAULT; |
| if (ua32_size != sizeof(*ua32)) |
| return -EINVAL; |
| |
| ua_size = sizeof(*ua); |
| ua = compat_alloc_user_space(ua_size); |
| if (!ua) |
| return -ENOMEM; |
| |
| /* Copy everything but data, then fixup size & data. */ |
| if (copy_in_user(ua, ua32, sizeof(*ua32) - sizeof(ua32->data))) |
| return -EFAULT; |
| if (put_user(ua_size, (uint32_t __user*)&ua->size)) |
| return -EFAULT; |
| if (get_user(ua32_data, (compat_caddr_t __user*)&ua32->data)) |
| return -EFAULT; |
| ua_data = compat_ptr(ua32_data); |
| if (put_user(ua_data, (void* __user*)&ua->data)) |
| return -EFAULT; |
| |
| switch (cmd) { |
| case CRUS_SP_IOCTL_GET32: |
| cmd64 = CRUS_SP_IOCTL_GET; |
| break; |
| case CRUS_SP_IOCTL_SET32: |
| cmd64 = CRUS_SP_IOCTL_SET; |
| break; |
| case CRUS_SP_IOCTL_GET_CALIB32: |
| cmd64 = CRUS_SP_IOCTL_GET_CALIB; |
| break; |
| case CRUS_SP_IOCTL_SET_CALIB32: |
| cmd64 = CRUS_SP_IOCTL_SET_CALIB; |
| break; |
| default: |
| pr_err("%s: Invalid IOCTL, command = %d!\n", __func__, cmd); |
| return -EINVAL; |
| } |
| |
| return crus_sp_shared_ioctl(f, cmd64, ua); |
| } |
| |
| static int crus_sp_open(struct inode *inode, struct file *f) |
| { |
| int result = 0; |
| pr_debug("%s\n", __func__); |
| |
| atomic_inc(&crus_sp_misc_usage_count); |
| return result; |
| } |
| |
| static int crus_sp_release(struct inode *inode, struct file *f) |
| { |
| int result = 0; |
| |
| pr_debug("%s\n", __func__); |
| |
| atomic_dec(&crus_sp_misc_usage_count); |
| pr_debug("%s: ref count %d!\n", __func__, |
| atomic_read(&crus_sp_misc_usage_count)); |
| |
| return result; |
| } |
| |
| static ssize_t |
| temperature_left_show(struct device *dev, struct device_attribute *a, char *buf) |
| { |
| static const int material = 250; |
| static const int scale_factor = 100000; |
| int buffer[96]; |
| int out_cal0; |
| int out_cal1; |
| int z, r, t; |
| int temp0; |
| |
| crus_afe_get_param(cirrus_ff_port, CIRRUS_SP, CRUS_PARAM_RX_GET_TEMP, |
| 384, buffer); |
| |
| out_cal0 = buffer[12]; |
| out_cal1 = buffer[13]; |
| |
| z = buffer[4]; |
| |
| temp0 = buffer[10]; |
| |
| if ((out_cal0 != 2) || (out_cal1 != 2)) |
| return sprintf(buf, "Calibration is not done\n"); |
| |
| r = buffer[3]; |
| t = (material * scale_factor * (r - z) / z) + (temp0 * scale_factor); |
| |
| return sprintf(buf, "%d.%05dc\n", t / scale_factor, t % scale_factor); |
| } |
| |
| static DEVICE_ATTR_RO(temperature_left); |
| |
| static ssize_t |
| temperature_right_show(struct device *dev, struct device_attribute *a, |
| char *buf) |
| { |
| static const int material = 250; |
| static const int scale_factor = 100000; |
| int buffer[96]; |
| int out_cal0; |
| int out_cal1; |
| int z, r, t; |
| int temp0; |
| |
| crus_afe_get_param(cirrus_ff_port, CIRRUS_SP, CRUS_PARAM_RX_GET_TEMP, |
| 384, buffer); |
| |
| out_cal0 = buffer[14]; |
| out_cal1 = buffer[15]; |
| |
| z = buffer[2]; |
| |
| temp0 = buffer[10]; |
| |
| if ((out_cal0 != 2) || (out_cal1 != 2)) |
| return sprintf(buf, "Calibration is not done\n"); |
| |
| r = buffer[1]; |
| t = (material * scale_factor * (r - z) / z) + (temp0 * scale_factor); |
| |
| return sprintf(buf, "%d.%05dc\n", t / scale_factor, t % scale_factor); |
| } |
| |
| static DEVICE_ATTR_RO(temperature_right); |
| |
| static ssize_t |
| resistance_left_show(struct device *dev, struct device_attribute *a, char *buf) |
| { |
| static const int scale_factor = 100000000; |
| static const int amp_factor = 71498; |
| int buffer[96]; |
| int out_cal0; |
| int out_cal1; |
| int r; |
| |
| crus_afe_get_param(cirrus_ff_port, CIRRUS_SP, CRUS_PARAM_RX_GET_TEMP, |
| 384, buffer); |
| |
| out_cal0 = buffer[12]; |
| out_cal1 = buffer[13]; |
| |
| if ((out_cal0 != 2) || (out_cal1 != 2)) |
| return sprintf(buf, "Calibration is not done\n"); |
| |
| r = buffer[3] * amp_factor; |
| |
| return sprintf(buf, "%d.%08d ohms\n", r / scale_factor, |
| r % scale_factor); |
| } |
| |
| static DEVICE_ATTR_RO(resistance_left); |
| |
| static ssize_t |
| resistance_right_show(struct device *dev, struct device_attribute *a, char *buf) |
| { |
| static const int scale_factor = 100000000; |
| static const int amp_factor = 71498; |
| int buffer[96]; |
| int out_cal0; |
| int out_cal1; |
| int r; |
| |
| crus_afe_get_param(cirrus_ff_port, CIRRUS_SP, CRUS_PARAM_RX_GET_TEMP, |
| 384, buffer); |
| |
| out_cal0 = buffer[14]; |
| out_cal1 = buffer[15]; |
| |
| if ((out_cal0 != 2) || (out_cal1 != 2)) |
| return sprintf(buf, "Calibration is not done\n"); |
| |
| r = buffer[1] * amp_factor; |
| |
| return sprintf(buf, "%d.%08d ohms\n", r / scale_factor, |
| r % scale_factor); |
| } |
| |
| static DEVICE_ATTR_RO(resistance_right); |
| |
| static struct attribute *crus_sp_attrs[] = { |
| &dev_attr_temperature_left.attr, |
| &dev_attr_temperature_right.attr, |
| &dev_attr_resistance_left.attr, |
| &dev_attr_resistance_right.attr, |
| &dev_attr_resistance_left_right.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group crus_sp_group = { |
| .attrs = crus_sp_attrs, |
| }; |
| |
| static const struct attribute_group *crus_sp_groups[] = { |
| &crus_sp_group, |
| NULL, |
| }; |
| |
| static void msm_crus_init_spk(void) |
| { |
| int i = 0, j = 0; |
| |
| pr_debug("%s: init ssr parameters", __func__); |
| |
| mutex_lock(&crus_sp_lock); |
| crus_spk.flag = 0; |
| for (i = 0; i < MAX_SPK_PORT; i++) |
| for (j = 0; j < MAX_TUNING_CONFIGS; j++) |
| crus_spk.bin_files[i][j] = NULL; |
| mutex_unlock(&crus_sp_lock); |
| } |
| |
| static int msm_crus_parse_spk(struct platform_device *pdev) |
| { |
| swap_calibration = of_property_read_bool(pdev->dev.of_node, "cirrus,swap_calibration"); |
| |
| if (of_property_read_u32(pdev->dev.of_node, "cirrus,top-speaker-impedance", &crus_cal.top_spk_impedance)) { |
| dev_err(&pdev->dev, "parse cirrus,top-speaker-impedance failed\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(&pdev->dev, "top of speaker impedance: %u\n", |
| crus_cal.top_spk_impedance); |
| |
| if (of_property_read_u32(pdev->dev.of_node, "cirrus,top-speaker-tolerance", &crus_cal.top_spk_tolerance)) { |
| dev_err(&pdev->dev, "parse cirrus,top-speaker-tolerance failed\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(&pdev->dev, "top of speaker tolerance: %u\n", |
| crus_cal.top_spk_tolerance); |
| |
| if (of_property_read_u32(pdev->dev.of_node, "cirrus,top-speaker-mean", &crus_cal.top_spk_mean)) { |
| dev_err(&pdev->dev, "parse cirrus,top-speaker-mean failed\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(&pdev->dev, "top of speaker mean: %u\n", |
| crus_cal.top_spk_mean); |
| |
| |
| if (of_property_read_u32(pdev->dev.of_node, "cirrus,bottom-speaker-impedance", &crus_cal.bottom_spk_impedance)) { |
| dev_err(&pdev->dev, "parse cirrus,bottom-speaker-impedance failed\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(&pdev->dev, "bottom of speaker impedance: %u\n", |
| crus_cal.bottom_spk_impedance); |
| |
| if (of_property_read_u32(pdev->dev.of_node, "cirrus,bottom-speaker-tolerance", &crus_cal.bottom_spk_tolerance)) { |
| dev_err(&pdev->dev, "parse cirrus,top-speaker-tolerance failed\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(&pdev->dev, "bottom of speaker tolerance: %u\n", |
| crus_cal.bottom_spk_tolerance); |
| |
| if (of_property_read_u32(pdev->dev.of_node, "cirrus,bottom-speaker-mean", &crus_cal.bottom_spk_mean)) { |
| dev_err(&pdev->dev, "parse cirrus,bottom-speaker-mean failed\n"); |
| return -EINVAL; |
| } |
| |
| dev_dbg(&pdev->dev, "bottom of speaker mean: %u\n", |
| crus_cal.bottom_spk_mean); |
| |
| return 0; |
| } |
| static int msm_cirrus_playback_probe(struct platform_device *pdev) |
| { |
| int i; |
| |
| pr_info("CRUS_SP: initializing platform device\n"); |
| |
| crus_sp_usecase_dt_count = of_property_count_strings(pdev->dev.of_node, |
| "usecase-names"); |
| if (crus_sp_usecase_dt_count <= 0) { |
| dev_dbg(&pdev->dev, "Usecase names not found\n"); |
| crus_sp_usecase_dt_count = 0; |
| return 0; |
| } |
| |
| if ((crus_sp_usecase_dt_count > 0) && |
| (crus_sp_usecase_dt_count <= MAX_TUNING_CONFIGS)) |
| of_property_read_string_array(pdev->dev.of_node, |
| "usecase-names", |
| crus_sp_usecase_dt_text, |
| crus_sp_usecase_dt_count); |
| else if (crus_sp_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_sp_usecase_dt_count; i++) |
| pr_info("CRUS_SP: usecase[%d] = %s\n", i, |
| crus_sp_usecase_dt_text[i]); |
| |
| if (msm_crus_parse_spk(pdev)) { |
| return -EINVAL; |
| } |
| |
| msm_crus_init_spk(); |
| 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_sp_fops = { |
| .owner = THIS_MODULE, |
| .open = crus_sp_open, |
| .release = crus_sp_release, |
| .unlocked_ioctl = crus_sp_ioctl, |
| .compat_ioctl = crus_sp_compat_ioctl, |
| }; |
| |
| struct miscdevice crus_sp_misc = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "msm_cirrus_playback", |
| .fops = &crus_sp_fops, |
| }; |
| |
| static int __init crus_sp_init(void) |
| { |
| pr_info("CRUS_SP_INIT: initializing misc device\n"); |
| atomic_set(&crus_sp_get_param_flag, 0); |
| atomic_set(&crus_sp_misc_usage_count, 0); |
| mutex_init(&crus_sp_get_param_lock); |
| mutex_init(&crus_sp_lock); |
| |
| misc_register(&crus_sp_misc); |
| |
| if (sysfs_create_groups(&crus_sp_misc.this_device->kobj, |
| crus_sp_groups)) |
| pr_err("%s: Could not create sysfs groups\n", __func__); |
| |
| return platform_driver_register(&msm_cirrus_playback_driver); |
| } |
| module_init(crus_sp_init); |
| |
| static void __exit crus_sp_exit(void) |
| { |
| int i = 0, j = 0; |
| mutex_destroy(&crus_sp_get_param_lock); |
| mutex_destroy(&crus_sp_lock); |
| for (i = 0; i < MAX_SPK_PORT; i++) |
| for (j = 0; j < MAX_TUNING_CONFIGS; j++) { |
| release_firmware(crus_spk.bin_files[i][j]); |
| crus_spk.bin_files[i][j] = NULL; |
| } |
| platform_driver_unregister(&msm_cirrus_playback_driver); |
| } |
| module_exit(crus_sp_exit); |