android_kernel_samsung_on5x.../drivers/net/wireless/scsc/cm_if.c
2018-06-19 23:16:04 +02:00

537 lines
15 KiB
C

/****************************************************************************
*
* Copyright (c) 2012 - 2016 Samsung Electronics Co., Ltd. All rights reserved
*
* Chip Manager interface
*
****************************************************************************/
#include "mgt.h"
#include "dev.h"
#include "debug.h"
#include "scsc_wifi_cm_if.h"
#ifdef CONFIG_SCSC_WLAN_DEBUG
#include "hip4_sampler.h"
#endif
#include "../scsc/scsc_mx_impl.h" /* TODO */
#include <scsc/scsc_mx.h>
static bool EnableTestMode;
module_param(EnableTestMode, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(EnableTestMode, "Enable WlanLite test mode driver.");
static BLOCKING_NOTIFIER_HEAD(slsi_wlan_notifier);
static bool Enable232338TestMode;
module_param(Enable232338TestMode, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(Enable232338TestMode, "Enable 2323338 RF test mode driver.");
static struct mutex slsi_start_mutex;
static struct mutex slsi_open_mutex;
static int recovery_in_progress;
static u16 latest_scsc_panic_code;
#define SLSI_SM_WLAN_SERVICE_STOP_RECOVERY_TIMEOUT 20000
/* TODO: Would be good to get this removed - use module_client? */
struct slsi_cm_ctx {
struct slsi_dev *sdev;
};
/* Only one wlan service instance is assumed for now. */
static struct slsi_cm_ctx cm_ctx;
static void slsi_hip_block_bh(struct slsi_dev *sdev);
int slsi_wlan_service_notifier_register(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&slsi_wlan_notifier, nb);
}
int slsi_wlan_service_notifier_unregister(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&slsi_wlan_notifier, nb);
}
static void wlan_stop_on_failure(struct scsc_service_client *client)
{
int state;
struct slsi_dev *sdev = container_of(client, struct slsi_dev, mx_wlan_client);
SLSI_INFO_NODEV("\n");
mutex_lock(&slsi_start_mutex);
recovery_in_progress = 1;
sdev->recovery_status = 1;
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_BLOCKED);
slsi_hip_block_bh(sdev);
sdev->fail_reported = true;
/* Stop wlan operations. Send event to registered parties */
mutex_unlock(&slsi_start_mutex);
SLSI_INFO_NODEV("Nofity registered functions\n");
blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_STOP, sdev);
mutex_lock(&slsi_start_mutex);
} else {
SLSI_INFO_NODEV("Wi-Fi service driver not started\n");
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
reinit_completion(&sdev->recovery_remove_completion);
reinit_completion(&sdev->recovery_stop_completion);
#else
INIT_COMPLETION(&sdev->recovery_remove_completion);
INIT_COMPLETION(&sdev->recovery_stop_completion);
#endif
mutex_unlock(&slsi_start_mutex);
}
static void wlan_failure_reset(struct scsc_service_client *client, u16 scsc_panic_code)
{
SLSI_INFO_NODEV("Enter\n");
latest_scsc_panic_code = scsc_panic_code;
}
int check_mcd_232338_rf_mode(void)
{
struct file *fp = NULL;
char *filepath = "/data/.psm.info";
char power_val = 0;
fp = filp_open(filepath, O_RDONLY, 0);
if (IS_ERR(fp) || (fp == NULL)) {
pr_err("%s is not exist.\n", filepath);
return -ENOENT; /* -2 */
}
kernel_read(fp, fp->f_pos, &power_val, 1);
/* if power_val is 0, it means rf_test mode by 232338. */
if (power_val == '0') {
pr_err("*#232338# is enabled.\n");
Enable232338TestMode = 1;
} else {
pr_err("*#232338# is disabled.\n");
Enable232338TestMode = 0;
}
if (fp)
filp_close(fp, NULL);
return 0;
}
/* WLAN service driver registration
* ================================
*/
void slsi_wlan_service_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason)
{
struct slsi_dev *sdev;
struct device *dev;
struct scsc_service_client mx_wlan_client;
SLSI_UNUSED_PARAMETER(module_client);
SLSI_INFO_NODEV("WLAN service probe\n");
mutex_lock(&slsi_start_mutex);
if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress)
goto done;
if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY) {
SLSI_INFO_NODEV("Probe recovery\n");
sdev = cm_ctx.sdev;
recovery_in_progress = 0;
sdev->fail_reported = false;
} else {
/* Register callbacks */
mx_wlan_client.stop_on_failure = wlan_stop_on_failure;
mx_wlan_client.failure_reset = wlan_failure_reset;
dev = scsc_mx_get_device(mx);
sdev = slsi_dev_attach(dev, mx, &mx_wlan_client);
if (sdev == NULL) {
SLSI_ERR_NODEV("WLAN attach failed - slsi_dev_attach\n");
goto done;
}
cm_ctx.sdev = sdev; /* TODO: For now. */
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBING);
get_device(dev);
#ifdef CONFIG_SCSC_WLAN_DEBUG
hip4_sampler_create(mx);
#endif
}
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBED);
done:
mutex_unlock(&slsi_start_mutex);
}
/* service_clean_up_locked expects the slsi_start_mutex mutex to be claimed when
* service_clean_up_locked is called.
*/
static void service_clean_up_locked(struct slsi_dev *sdev)
{
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_REMOVING);
put_device(sdev->dev);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_REMOVED);
sdev->maxwell_core = NULL;
/* The mutex must be released at this point since the tear down
* process will call various functions including
* slsi_sm_wlan_service_stop and slsi_sm_wlan_service_close, which will
* claim the same mutex.
*/
mutex_unlock(&slsi_start_mutex);
slsi_dev_detach(sdev);
mutex_lock(&slsi_start_mutex);
}
static void slsi_wlan_service_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx, enum scsc_module_client_reason reason)
{
struct slsi_dev *sdev;
int state;
SLSI_UNUSED_PARAMETER(mx);
SLSI_UNUSED_PARAMETER(module_client);
sdev = cm_ctx.sdev;
if (!sdev) {
SLSI_INFO_NODEV("no sdev\n");
return;
}
if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && !recovery_in_progress) {
SLSI_INFO_NODEV("WLAN service remove - recovery. Service not active.\n");
} else if (reason == SCSC_MODULE_CLIENT_REASON_RECOVERY && recovery_in_progress) {
int r;
SLSI_INFO_NODEV("WLAN service remove - recovery\n");
SLSI_INFO_NODEV("Nofity registered functions\n");
blocking_notifier_call_chain(&slsi_wlan_notifier, SCSC_WIFI_FAILURE_RESET, sdev);
mutex_lock(&slsi_start_mutex);
/**
* If there was a request to stop during the recovery, then do
* not sent a hang - just stop here. The Wi-Fi service driver is
* ready to be turned on again. Let the service_stop complete.
*/
complete_all(&sdev->recovery_remove_completion);
if (sdev->recovery_next_state == SCSC_WIFI_CM_IF_STATE_STOPPING) {
SLSI_INFO_NODEV("Recovery - next state stopping\n");
} else {
SLSI_INFO_NODEV("Calling slsi_send_hanged_vendor_event with latest_scsc_panic_code=0x%x\n",
latest_scsc_panic_code);
if (slsi_send_hanged_vendor_event(sdev, latest_scsc_panic_code) < 0)
SLSI_ERR(sdev, "Failed to send hang event\n");
/* Complete any pending ctrl signals, which will prevent
* the hang event from being processed.
*/
complete_all(&sdev->sig_wait.completion);
}
mutex_unlock(&slsi_start_mutex);
r = wait_for_completion_timeout(&sdev->recovery_stop_completion,
msecs_to_jiffies(SLSI_SM_WLAN_SERVICE_STOP_RECOVERY_TIMEOUT));
if (r == 0)
SLSI_INFO(sdev, "recovery_stop_completion timeout\n");
} else {
SLSI_INFO_NODEV("WLAN service remove\n");
#ifdef CONFIG_SCSC_WLAN_DEBUG
hip4_sampler_destroy(mx);
#endif
mutex_lock(&slsi_start_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_STARTED &&
state != SCSC_WIFI_CM_IF_STATE_PROBED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPED &&
state != SCSC_WIFI_CM_IF_STATE_BLOCKED) {
mutex_unlock(&slsi_start_mutex);
SLSI_INFO_NODEV("state-event error %d\n", state);
return;
}
service_clean_up_locked(sdev);
mutex_unlock(&slsi_start_mutex);
}
}
/* Block future HIP runs through the hip_switch */
static void slsi_hip_block_bh(struct slsi_dev *sdev)
{
SLSI_WARN(sdev, "HIP state set to #SLSI_HIP_STATE_BLOCKED#\n");
atomic_set(&sdev->hip.hip_state, SLSI_HIP_STATE_BLOCKED);
}
struct scsc_mx_module_client wlan_driver = {
.name = "WLAN driver",
.probe = slsi_wlan_service_probe,
.remove = slsi_wlan_service_remove,
};
int slsi_sm_service_driver_register(void)
{
struct slsi_cm_ctx *ctx = &cm_ctx;
memset(ctx, 0, sizeof(*ctx));
mutex_init(&slsi_start_mutex);
mutex_init(&slsi_open_mutex);
scsc_mx_module_register_client_module(&wlan_driver);
return 0;
}
void slsi_sm_service_driver_unregister(void)
{
scsc_mx_module_unregister_client_module(&wlan_driver);
}
/* start/stop wlan service
* =======================
*/
void slsi_sm_service_failed(struct slsi_dev *sdev, const char *reason)
{
int state;
mutex_lock(&slsi_start_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_STARTED &&
state != SCSC_WIFI_CM_IF_STATE_STARTING &&
state != SCSC_WIFI_CM_IF_STATE_PROBED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPING) {
mutex_unlock(&slsi_start_mutex);
SLSI_INFO(sdev, "State-event error %d\n", state);
return;
}
/* Limit the volume of error reports to the core */
if (!sdev->fail_reported) {
/* This log may be scraped by test systems */
SLSI_ERR(sdev, "scsc_wifibt: FATAL ERROR: %s\n", reason);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_BLOCKED);
slsi_hip_block_bh(sdev);
scsc_mx_service_service_failed(sdev->service);
sdev->fail_reported = true;
}
mutex_unlock(&slsi_start_mutex);
}
/* Is production test mode enabled? */
bool slsi_is_test_mode_enabled(void)
{
return EnableTestMode;
}
/* Is production 232338 test mode enabled? */
bool slsi_is_232338_test_mode_enabled(void)
{
return Enable232338TestMode;
}
int slsi_sm_wlan_service_open(struct slsi_dev *sdev)
{
int err = 0;
int state;
mutex_lock(&slsi_open_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_PROBED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
SLSI_INFO(sdev, "State-event error %d\n", state);
err = -EINVAL;
goto exit;
}
/* Open service - will download FW - will set MBOX0 with Starting address */
SLSI_INFO(sdev, "Open WLAN service\n");
sdev->service = scsc_mx_service_open(sdev->maxwell_core, SCSC_SERVICE_ID_WLAN, &sdev->mx_wlan_client, &err);
if (!sdev->service) {
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_PROBED);
SLSI_WARN(sdev, "Service open failed\n");
err = -EINVAL;
goto exit;
}
exit:
mutex_unlock(&slsi_open_mutex);
return err;
}
int slsi_sm_wlan_service_start(struct slsi_dev *sdev)
{
struct slsi_hip4 *hip = &sdev->hip4_inst;
scsc_mifram_ref ref;
int err = 0;
int err2 = 0;
int state;
mutex_lock(&slsi_start_mutex);
state = atomic_read(&sdev->cm_if.cm_if_state);
if (state != SCSC_WIFI_CM_IF_STATE_PROBED &&
state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
SLSI_INFO(sdev, "State-event error %d\n", state);
mutex_unlock(&slsi_start_mutex);
return -EINVAL;
}
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STARTING);
/* Get RAM from the MIF */
SLSI_INFO(sdev, "Allocate mifram\n");
err = scsc_mx_service_mifram_alloc(sdev->service, 1.5 * 1024 * 1024, &sdev->hip4_inst.hip_ref, 4096);
if (err) {
SLSI_WARN(sdev, "scsc_mx_service_mifram_alloc failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
mutex_unlock(&slsi_start_mutex);
return err;
}
SLSI_INFO(sdev, "Start HIP\n");
err = slsi_hip_start(sdev, NULL);
if (err) {
SLSI_WARN(sdev, "slsi_hip_start failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
err = scsc_mx_service_mif_ptr_to_addr(sdev->service, hip->hip_control, &ref);
if (err) {
SLSI_WARN(sdev, "scsc_mx_service_mif_ptr_to_addr failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
SLSI_INFO(sdev, "Starting WLAN service\n");
err = scsc_mx_service_start(sdev->service, ref);
if (err) {
SLSI_WARN(sdev, "scsc_mx_service_start failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
err = slsi_hip_setup(sdev);
if (err) {
SLSI_WARN(sdev, "slsi_hip_setup failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
SLSI_INFO_NODEV("Stopping WLAN service\n");
err2 = scsc_mx_service_stop(sdev->service);
if (err2)
SLSI_INFO(sdev, "scsc_mx_service_stop failed err2: %d\n", err2);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
/* Service has started, inform SAP versions to the registered SAPs */
err = slsi_hip_sap_setup(sdev);
if (err) {
SLSI_WARN(sdev, "slsi_hip_sap_setup failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
SLSI_INFO_NODEV("Stopping WLAN service\n");
err2 = scsc_mx_service_stop(sdev->service);
if (err2)
SLSI_INFO(sdev, "scsc_mx_service_stop failed err2: %d\n", err2);
slsi_hip_stop(sdev);
mutex_unlock(&slsi_start_mutex);
return err;
}
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STARTED);
mutex_unlock(&slsi_start_mutex);
return 0;
}
void slsi_sm_wlan_service_stop(struct slsi_dev *sdev)
{
int cm_if_state;
int err;
mutex_lock(&slsi_start_mutex);
cm_if_state = atomic_read(&sdev->cm_if.cm_if_state);
if (cm_if_state == SCSC_WIFI_CM_IF_STATE_BLOCKED) {
int r;
sdev->recovery_next_state = SCSC_WIFI_CM_IF_STATE_STOPPING;
mutex_unlock(&slsi_start_mutex);
r = wait_for_completion_timeout(&sdev->recovery_remove_completion,
msecs_to_jiffies(SLSI_SM_WLAN_SERVICE_STOP_RECOVERY_TIMEOUT));
if (r == 0)
SLSI_INFO(sdev, "recovery_remove_completion timeout\n");
mutex_lock(&slsi_start_mutex);
sdev->recovery_next_state = SCSC_WIFI_CM_IF_STATE_STOPPED;
/* If the wait hasn't timed out, then the cm_if_state will be
* set to probed here. If the probe hasn't fired for some reason
* try and do a service_stop regardless, since that's all we can
* do in this situation; hence skip the state check.
*/
goto skip_state_check;
}
if (cm_if_state != SCSC_WIFI_CM_IF_STATE_STARTED &&
cm_if_state != SCSC_WIFI_CM_IF_STATE_REMOVED &&
cm_if_state != SCSC_WIFI_CM_IF_STATE_PROBED) {
SLSI_INFO(sdev, "Service not started or incorrect state %d\n",
cm_if_state);
goto exit;
}
skip_state_check:
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPING);
SLSI_INFO_NODEV("Stopping WLAN service\n");
err = scsc_mx_service_stop(sdev->service);
if (err)
SLSI_INFO(sdev, "scsc_mx_service_stop failed err: %d\n", err);
atomic_set(&sdev->cm_if.cm_if_state, SCSC_WIFI_CM_IF_STATE_STOPPED);
exit:
mutex_unlock(&slsi_start_mutex);
}
void slsi_sm_wlan_service_close(struct slsi_dev *sdev)
{
int cm_if_state;
mutex_lock(&slsi_start_mutex);
cm_if_state = atomic_read(&sdev->cm_if.cm_if_state);
if (cm_if_state != SCSC_WIFI_CM_IF_STATE_STOPPED) {
SLSI_INFO(sdev, "Service not stopped\n");
goto exit;
}
SLSI_INFO_NODEV("Closing WLAN service\n");
scsc_mx_service_mifram_free(sdev->service, sdev->hip4_inst.hip_ref);
scsc_mx_service_close(sdev->service);
if (recovery_in_progress)
complete_all(&sdev->recovery_stop_completion);
exit:
mutex_unlock(&slsi_start_mutex);
}
void slsi_hydra_get_chip_info(struct hydra_service_info *chip_info)
{
if (chip_info)
memset(chip_info, 0, sizeof(*chip_info));
}