lineage_kernel_xcoverpro/drivers/misc/samsung/scsc/scsc_mx_module.c

202 lines
5.9 KiB
C
Executable File

/****************************************************************************
*
* Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved
*
****************************************************************************/
#include <linux/slab.h>
#include <linux/module.h>
#include <scsc/scsc_mx.h>
#include <scsc/scsc_release.h>
#include <scsc/scsc_logring.h>
#include "scsc_mif_abs.h"
#include "scsc_mx_impl.h"
#ifdef CONFIG_SCSC_WLBTD
#include "scsc_wlbtd.h"
#endif
#define SCSC_MX_CORE_MODDESC "mx140 Core Driver"
struct clients_node {
struct list_head list;
struct scsc_mx_module_client *module_client;
};
struct mx_node {
struct list_head list;
struct scsc_mx *mx;
};
static struct mx_module {
struct list_head clients_list;
struct list_head mx_list;
} mx_module = {
.clients_list = LIST_HEAD_INIT(mx_module.clients_list),
.mx_list = LIST_HEAD_INIT(mx_module.mx_list)
};
static void scsc_mx_module_probe_registered_clients(struct scsc_mx *new_mx)
{
bool client_registered = false;
struct clients_node *client_node, *client_next;
/* Traverse Linked List for each mif_driver node */
list_for_each_entry_safe(client_node, client_next, &mx_module.clients_list, list) {
client_node->module_client->probe(client_node->module_client, new_mx, SCSC_MODULE_CLIENT_REASON_HW_PROBE);
client_registered = true;
}
if (client_registered == false)
SCSC_TAG_INFO(MXMAN, "No clients registered\n");
}
static void scsc_mx_module_probe(struct scsc_mif_abs_driver *abs_driver, struct scsc_mif_abs *mif_abs)
{
struct scsc_mx *new_mx;
struct mx_node *mx_node;
/* Avoid unused parm error */
(void)abs_driver;
mx_node = kzalloc(sizeof(*mx_node), GFP_KERNEL);
if (!mx_node)
return;
/* Create new mx instance */
new_mx = scsc_mx_create(mif_abs);
if (!new_mx) {
kfree(mx_node);
SCSC_TAG_ERR(MXMAN, "Error allocating new_mx\n");
return;
}
/* Add instance in mx_node linked list */
mx_node->mx = new_mx;
list_add_tail(&mx_node->list, &mx_module.mx_list);
scsc_mx_module_probe_registered_clients(new_mx);
}
static void scsc_mx_module_remove(struct scsc_mif_abs *abs)
{
bool match = false;
struct mx_node *mx_node, *next;
/* Traverse Linked List for each mx node */
list_for_each_entry_safe(mx_node, next, &mx_module.mx_list, list) {
/* If there is a match, call destroy */
if (scsc_mx_get_mif_abs(mx_node->mx) == abs) {
match = true;
scsc_mx_destroy(mx_node->mx);
list_del(&mx_node->list);
kfree(mx_node);
}
}
if (match == false)
SCSC_TAG_ERR(MXMAN, "FATAL, no match for given scsc_mif_abs\n");
}
static struct scsc_mif_abs_driver mx_module_mif_if = {
.name = "mx140 driver",
.probe = scsc_mx_module_probe,
.remove = scsc_mx_module_remove,
};
static int __init scsc_mx_module_init(void)
{
SCSC_TAG_INFO(MXMAN, SCSC_MX_CORE_MODDESC " scsc_release %d.%d.%d.%d.%d\n",
SCSC_RELEASE_PRODUCT,
SCSC_RELEASE_ITERATION,
SCSC_RELEASE_CANDIDATE,
SCSC_RELEASE_POINT,
SCSC_RELEASE_CUSTOMER);
scsc_mif_abs_register(&mx_module_mif_if);
return 0;
}
static void __exit scsc_mx_module_exit(void)
{
struct mx_node *mx_node, *next_mx;
/* Traverse Linked List for each mx node */
list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list) {
scsc_mx_destroy(mx_node->mx);
list_del(&mx_node->list);
kfree(mx_node);
}
scsc_mif_abs_unregister(&mx_module_mif_if);
SCSC_TAG_INFO(MXMAN, SCSC_MX_CORE_MODDESC " unloaded\n");
}
/**
* Reset all registered service drivers by first calling the remove callback and
* then the probe callback. This function is used during recovery operations,
* where the chip has been reset as part of the recovery and the service drivers
* has to do the same.
*/
int scsc_mx_module_reset(void)
{
struct clients_node *clients_node;
struct mx_node *mx_node, *next_mx;
/* Traverse Linked List and call registered removed callbacks */
list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list)
list_for_each_entry(clients_node, &mx_module.clients_list, list)
clients_node->module_client->remove(clients_node->module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_RECOVERY);
/* Traverse Linked List and call registered probed callbacks */
list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list)
list_for_each_entry(clients_node, &mx_module.clients_list, list)
clients_node->module_client->probe(clients_node->module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_RECOVERY);
return 0;
}
EXPORT_SYMBOL(scsc_mx_module_reset);
int scsc_mx_module_register_client_module(struct scsc_mx_module_client *module_client)
{
struct clients_node *module_client_node;
struct mx_node *mx_node;
/* Add node in modules linked list */
module_client_node = kzalloc(sizeof(*module_client_node), GFP_KERNEL);
if (!module_client_node)
return -ENOMEM;
module_client_node->module_client = module_client;
list_add_tail(&module_client_node->list, &mx_module.clients_list);
/* Traverse Linked List for each mx node */
list_for_each_entry(mx_node, &mx_module.mx_list, list) {
module_client->probe(module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_HW_PROBE);
}
return 0;
}
EXPORT_SYMBOL(scsc_mx_module_register_client_module);
void scsc_mx_module_unregister_client_module(struct scsc_mx_module_client *module_client)
{
struct clients_node *client_node, *client_next;
struct mx_node *mx_node, *next_mx;
/* Traverse Linked List for each client_list */
list_for_each_entry_safe(client_node, client_next, &mx_module.clients_list, list) {
if (client_node->module_client == module_client) {
list_for_each_entry_safe(mx_node, next_mx, &mx_module.mx_list, list) {
module_client->remove(module_client, mx_node->mx, SCSC_MODULE_CLIENT_REASON_HW_REMOVE);
}
list_del(&client_node->list);
kfree(client_node);
}
}
}
EXPORT_SYMBOL(scsc_mx_module_unregister_client_module);
module_init(scsc_mx_module_init);
module_exit(scsc_mx_module_exit);
MODULE_DESCRIPTION(SCSC_MX_CORE_MODDESC);
MODULE_AUTHOR("SCSC");
MODULE_LICENSE("GPL");