397 lines
9.5 KiB
C
Executable File
397 lines
9.5 KiB
C
Executable File
/****************************************************************************
|
|
*
|
|
* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/fs.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <scsc/scsc_logring.h>
|
|
#include <scsc/scsc_mx.h>
|
|
|
|
#include "mxman.h" /* Special case service driver that looks inside mxman */
|
|
|
|
#ifdef CONFIG_SCSC_FM_TEST
|
|
#include "mx250_fm_test.h"
|
|
#endif
|
|
|
|
|
|
struct scsc_mx_fm_client {
|
|
/* scsc_service_client has to be the first */
|
|
struct scsc_service_client fm_service_client;
|
|
struct scsc_service *fm_service;
|
|
struct scsc_mx *mx;
|
|
bool fm_api_available;
|
|
scsc_mifram_ref ref;
|
|
struct workqueue_struct *fm_client_wq;
|
|
struct work_struct fm_client_work;
|
|
struct completion fm_client_work_completion;
|
|
int fm_client_work_completion_status;
|
|
bool ldo_on;
|
|
};
|
|
|
|
static struct scsc_mx_fm_client *fm_client;
|
|
/* service to start */
|
|
static int service_id = SCSC_SERVICE_ID_FM;
|
|
|
|
static DEFINE_MUTEX(ss_lock);
|
|
|
|
static u8 fm_client_failure_notification(struct scsc_service_client *client, struct mx_syserr_decode *err)
|
|
{
|
|
(void)client;
|
|
SCSC_TAG_DEBUG(FM, "OK\n");
|
|
return err->level;
|
|
}
|
|
|
|
|
|
static bool fm_client_stop_on_failure(struct scsc_service_client *client, struct mx_syserr_decode *err)
|
|
{
|
|
(void)client;
|
|
(void)err;
|
|
mutex_lock(&ss_lock);
|
|
fm_client->fm_api_available = false;
|
|
mutex_unlock(&ss_lock);
|
|
SCSC_TAG_DEBUG(FM, "OK\n");
|
|
return false;
|
|
}
|
|
|
|
static void fm_client_failure_reset(struct scsc_service_client *client, u8 level, u16 scsc_syserr_code)
|
|
{
|
|
(void)client;
|
|
(void)level;
|
|
(void)scsc_syserr_code;
|
|
SCSC_TAG_DEBUG(FM, "OK\n");
|
|
}
|
|
|
|
static int stop_close_service(void)
|
|
{
|
|
int r;
|
|
|
|
if (!fm_client->fm_service) {
|
|
SCSC_TAG_ERR(FM, "No fm_service\n");
|
|
r = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
r = scsc_mx_service_stop(fm_client->fm_service);
|
|
if (r) {
|
|
SCSC_TAG_ERR(FM, "scsc_mx_service_stop(fm_service) failed %d\n", r);
|
|
goto done;
|
|
}
|
|
SCSC_TAG_DEBUG(FM, "scsc_mx_service_stop(fm_service) OK\n");
|
|
|
|
scsc_mx_service_mifram_free(fm_client->fm_service, fm_client->ref);
|
|
|
|
r = scsc_mx_service_close(fm_client->fm_service);
|
|
if (r) {
|
|
SCSC_TAG_ERR(FM, "scsc_mx_service_close(fm_service) failed %d\n", r);
|
|
goto done;
|
|
} else
|
|
SCSC_TAG_DEBUG(FM, "scsc_mx_service_close(fm_service) OK\n");
|
|
|
|
fm_client->fm_service = NULL;
|
|
fm_client->ref = 0;
|
|
done:
|
|
return r;
|
|
}
|
|
|
|
static int open_start_service(void)
|
|
{
|
|
struct scsc_service *fm_service;
|
|
int r;
|
|
int r2;
|
|
struct fm_ldo_conf *ldo_conf;
|
|
scsc_mifram_ref ref;
|
|
|
|
fm_service = scsc_mx_service_open(fm_client->mx, service_id, &fm_client->fm_service_client, &r);
|
|
if (!fm_service) {
|
|
r = -EINVAL;
|
|
SCSC_TAG_ERR(FM, "scsc_mx_service_open(fm_service) failed %d\n", r);
|
|
goto done;
|
|
}
|
|
/* Allocate memory */
|
|
r = scsc_mx_service_mifram_alloc(fm_service, sizeof(struct fm_ldo_conf), &ref, 32);
|
|
if (r) {
|
|
SCSC_TAG_ERR(FM, "scsc_mx_service_mifram_alloc(fm_service) failed %d\n", r);
|
|
r2 = scsc_mx_service_close(fm_service);
|
|
if (r2)
|
|
SCSC_TAG_ERR(FM, "scsc_mx_service_close(fm_service) failed %d\n", r2);
|
|
goto done;
|
|
}
|
|
ldo_conf = (struct fm_ldo_conf *)scsc_mx_service_mif_addr_to_ptr(fm_service, ref);
|
|
ldo_conf->version = FM_LDO_CONFIG_VERSION;
|
|
ldo_conf->ldo_on = fm_client->ldo_on;
|
|
|
|
r = scsc_mx_service_start(fm_service, ref);
|
|
if (r) {
|
|
SCSC_TAG_ERR(FM, "scsc_mx_service_start(fm_service) failed %d\n", r);
|
|
r2 = scsc_mx_service_close(fm_service);
|
|
if (r2)
|
|
SCSC_TAG_ERR(FM, "scsc_mx_service_close(fm_service) failed %d\n", r2);
|
|
scsc_mx_service_mifram_free(fm_service, ref);
|
|
goto done;
|
|
}
|
|
|
|
fm_client->fm_service = fm_service;
|
|
fm_client->ref = ref;
|
|
done:
|
|
return r;
|
|
}
|
|
|
|
static int open_start_close_service(void)
|
|
{
|
|
int r;
|
|
|
|
r = open_start_service();
|
|
if (r) {
|
|
SCSC_TAG_ERR(FM, "Error starting service: open_start_service(fm_service) failed %d\n", r);
|
|
|
|
if (!fm_client->ldo_on) {
|
|
/* Do not return here. For the case where WLBT FW is crashed, and FM off request is
|
|
* rejected, it's safest to continue to let scsc_service_on_halt_ldos_off() reset
|
|
* the global flag to indicate that FM is no longer needed when WLBT next boots.
|
|
* Otherwise LDO could be stuck always-on.
|
|
*/
|
|
} else
|
|
return r;
|
|
}
|
|
|
|
if (fm_client->ldo_on) {
|
|
/* FM turning on */
|
|
mxman_fm_on_halt_ldos_on();
|
|
|
|
} else {
|
|
/* FM turning off */
|
|
mxman_fm_on_halt_ldos_off();
|
|
|
|
/* Invalidate stored FM params */
|
|
mxman_fm_set_params(NULL);
|
|
}
|
|
|
|
r = stop_close_service();
|
|
if (r) {
|
|
SCSC_TAG_ERR(FM, "Error starting service: stop_close_service(fm_service) failed %d\n", r);
|
|
return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void fm_client_work_func(struct work_struct *work)
|
|
{
|
|
SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__);
|
|
|
|
fm_client->fm_client_work_completion_status = open_start_close_service();
|
|
if (fm_client->fm_client_work_completion_status) {
|
|
SCSC_TAG_ERR(FM, "open_start_close_service(fm_service) failed %d\n",
|
|
fm_client->fm_client_work_completion_status);
|
|
} else {
|
|
SCSC_TAG_DEBUG(FM, "OK\n");
|
|
}
|
|
complete(&fm_client->fm_client_work_completion);
|
|
|
|
}
|
|
|
|
static void fm_client_wq_init(void)
|
|
{
|
|
fm_client->fm_client_wq = create_singlethread_workqueue("fm_client_wq");
|
|
INIT_WORK(&fm_client->fm_client_work, fm_client_work_func);
|
|
}
|
|
|
|
static void fm_client_wq_stop(void)
|
|
{
|
|
cancel_work_sync(&fm_client->fm_client_work);
|
|
flush_workqueue(fm_client->fm_client_wq);
|
|
}
|
|
|
|
static void fm_client_wq_deinit(void)
|
|
{
|
|
fm_client_wq_stop();
|
|
destroy_workqueue(fm_client->fm_client_wq);
|
|
}
|
|
|
|
static void fm_client_wq_start(void)
|
|
{
|
|
queue_work(fm_client->fm_client_wq, &fm_client->fm_client_work);
|
|
}
|
|
|
|
static int fm_client_wq_start_blocking(void)
|
|
{
|
|
SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__);
|
|
|
|
fm_client_wq_start();
|
|
wait_for_completion(&fm_client->fm_client_work_completion);
|
|
if (fm_client->fm_client_work_completion_status) {
|
|
SCSC_TAG_ERR(FM, "%s failed: fm_client_wq_completion_status = %d\n",
|
|
__func__, fm_client->fm_client_work_completion_status);
|
|
return fm_client->fm_client_work_completion_status;
|
|
}
|
|
SCSC_TAG_DEBUG(FM, "OK\n");
|
|
return 0;
|
|
}
|
|
|
|
static int mx250_fm_re(bool ldo_on)
|
|
{
|
|
int r;
|
|
|
|
mutex_lock(&ss_lock);
|
|
SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__);
|
|
if (!fm_client) {
|
|
SCSC_TAG_ERR(FM, "fm_client = NULL\n");
|
|
mutex_unlock(&ss_lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!fm_client->fm_api_available) {
|
|
SCSC_TAG_WARNING(FM, "FM LDO API unavailable\n");
|
|
mutex_unlock(&ss_lock);
|
|
return -EAGAIN;
|
|
}
|
|
fm_client->ldo_on = ldo_on;
|
|
reinit_completion(&fm_client->fm_client_work_completion);
|
|
r = fm_client_wq_start_blocking();
|
|
mutex_unlock(&ss_lock);
|
|
return r;
|
|
|
|
}
|
|
|
|
/*
|
|
* FM Radio is starting, tell WLBT drivers
|
|
*/
|
|
int mx250_fm_request(void)
|
|
{
|
|
|
|
SCSC_TAG_INFO(FM, "request\n");
|
|
return mx250_fm_re(true);
|
|
}
|
|
EXPORT_SYMBOL(mx250_fm_request);
|
|
|
|
/*
|
|
* FM Radio is stopping, tell WLBT drivers
|
|
*/
|
|
int mx250_fm_release(void)
|
|
{
|
|
SCSC_TAG_INFO(FM, "release\n");
|
|
return mx250_fm_re(false);
|
|
}
|
|
EXPORT_SYMBOL(mx250_fm_release);
|
|
|
|
/*
|
|
* FM Radio parameters are changing, tell WLBT drivers
|
|
*/
|
|
void mx250_fm_set_params(struct wlbt_fm_params *info)
|
|
{
|
|
SCSC_TAG_DEBUG(FM, "mx250: %s\n", __func__);
|
|
|
|
if (!info)
|
|
return;
|
|
|
|
mutex_lock(&ss_lock);
|
|
|
|
SCSC_TAG_INFO(FM, "freq %u\n", info->freq);
|
|
|
|
mxman_fm_set_params(info);
|
|
|
|
mutex_unlock(&ss_lock);
|
|
}
|
|
EXPORT_SYMBOL(mx250_fm_set_params);
|
|
|
|
void fm_client_module_probe(struct scsc_mx_module_client *module_client, struct scsc_mx *mx,
|
|
enum scsc_module_client_reason reason)
|
|
{
|
|
/* Avoid unused error */
|
|
(void)module_client;
|
|
|
|
SCSC_TAG_INFO(FM, "probe\n");
|
|
|
|
mutex_lock(&ss_lock);
|
|
if (reason == SCSC_MODULE_CLIENT_REASON_HW_PROBE) {
|
|
fm_client = kzalloc(sizeof(*fm_client), GFP_KERNEL);
|
|
if (!fm_client) {
|
|
mutex_unlock(&ss_lock);
|
|
return;
|
|
}
|
|
init_completion(&fm_client->fm_client_work_completion);
|
|
fm_client_wq_init();
|
|
fm_client->fm_service_client.failure_notification = fm_client_failure_notification;
|
|
fm_client->fm_service_client.stop_on_failure_v2 = fm_client_stop_on_failure;
|
|
fm_client->fm_service_client.failure_reset_v2 = fm_client_failure_reset;
|
|
fm_client->mx = mx;
|
|
}
|
|
fm_client->fm_api_available = true;
|
|
SCSC_TAG_DEBUG(FM, "OK\n");
|
|
mutex_unlock(&ss_lock);
|
|
}
|
|
|
|
void fm_client_module_remove(struct scsc_mx_module_client *module_client, struct scsc_mx *mx,
|
|
enum scsc_module_client_reason reason)
|
|
{
|
|
/* Avoid unused error */
|
|
(void)module_client;
|
|
|
|
SCSC_TAG_INFO(FM, "remove\n");
|
|
|
|
mutex_lock(&ss_lock);
|
|
if (reason == SCSC_MODULE_CLIENT_REASON_HW_REMOVE) {
|
|
if (!fm_client) {
|
|
mutex_unlock(&ss_lock);
|
|
return;
|
|
}
|
|
if (fm_client->mx != mx) {
|
|
SCSC_TAG_ERR(FM, "fm_client->mx != mx\n");
|
|
mutex_unlock(&ss_lock);
|
|
return;
|
|
}
|
|
fm_client_wq_deinit();
|
|
kfree(fm_client);
|
|
fm_client = NULL;
|
|
}
|
|
SCSC_TAG_DEBUG(FM, "OK\n");
|
|
mutex_unlock(&ss_lock);
|
|
}
|
|
|
|
/* FM client driver registration */
|
|
struct scsc_mx_module_client fm_client_driver = {
|
|
.name = "FM client driver",
|
|
.probe = fm_client_module_probe,
|
|
.remove = fm_client_module_remove,
|
|
};
|
|
|
|
static int __init scsc_fm_client_module_init(void)
|
|
{
|
|
int r;
|
|
|
|
SCSC_TAG_INFO(FM, "init\n");
|
|
|
|
r = scsc_mx_module_register_client_module(&fm_client_driver);
|
|
if (r) {
|
|
SCSC_TAG_ERR(FM, "scsc_mx_module_register_client_module failed: r=%d\n", r);
|
|
return r;
|
|
}
|
|
#ifdef CONFIG_SCSC_FM_TEST
|
|
mx250_fm_test_init();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void __exit scsc_fm_client_module_exit(void)
|
|
{
|
|
SCSC_TAG_INFO(FM, "exit\n");
|
|
scsc_mx_module_unregister_client_module(&fm_client_driver);
|
|
#ifdef CONFIG_SCSC_FM_TEST
|
|
mx250_fm_test_exit();
|
|
#endif
|
|
SCSC_TAG_DEBUG(FM, "exit\n");
|
|
}
|
|
|
|
late_initcall(scsc_fm_client_module_init);
|
|
module_exit(scsc_fm_client_module_exit);
|
|
|
|
MODULE_DESCRIPTION("FM Client Driver");
|
|
MODULE_AUTHOR("SCSC");
|
|
MODULE_LICENSE("GPL");
|