1291 lines
34 KiB
C
1291 lines
34 KiB
C
/* 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.
|
|
*/
|
|
#define PACMAN_VERSION "1.1"
|
|
#define CLASS_NAME "msm_pacman"
|
|
#define DEVICE_NAME "msm_pacman"
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
|
|
#include <soc/qcom/scm.h>
|
|
#include "msm_pacman_qmi_interface.h"
|
|
|
|
struct pacman_control_s {
|
|
dev_t dev_num;
|
|
struct class *dev_class;
|
|
struct device *dev;
|
|
};
|
|
static struct pacman_control_s pacman_ctl;
|
|
|
|
/******************************************************************************
|
|
* PACMan Configuration
|
|
******************************************************************************/
|
|
/* Number of QUPs supported */
|
|
#define NUM_QUPS 12
|
|
#define READ_BUF_SZ 256
|
|
#define WRITE_BUF_SZ 64
|
|
|
|
/* Bypass bus bind/unbind calls and return success */
|
|
/* #define DEBUG_BYPASS_BINDING */
|
|
/* Bypass TrustZone calls and return success */
|
|
/* #define DEBUG_BYPASS_TZ */
|
|
/* Bypass QMI calls and return success */
|
|
/* #define DEBUG_BYPASS_QMI */
|
|
|
|
/******************************************************************************
|
|
* Trust Zone API
|
|
******************************************************************************/
|
|
#ifdef DEBUG_BYPASS_TZ
|
|
static int tz_configure_blsp_ownership(u32 qup_id, u32 subsystem_id)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
static int tz_configure_blsp_ownership(u32 qup_id, u32 subsystem_id)
|
|
{
|
|
int rc;
|
|
struct scm_desc desc = {0};
|
|
|
|
desc.arginfo = 2;
|
|
desc.args[0] = qup_id;
|
|
desc.args[1] = subsystem_id;
|
|
|
|
rc = scm_call2(SCM_SIP_FNID(4, 3), &desc);
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
/* State Machine Input Events (edges) */
|
|
enum state_machine_input_t {
|
|
INIT = 0,
|
|
ENABLE = 1,
|
|
DISABLE = 2,
|
|
SSR = 3
|
|
};
|
|
|
|
struct state_machine_t {
|
|
void (*curr_state)(struct state_machine_t *sm,
|
|
enum state_machine_input_t input,
|
|
void *arg);
|
|
void (*next_state)(struct state_machine_t *sm,
|
|
enum state_machine_input_t input,
|
|
void *arg);
|
|
char *curr_state_string;
|
|
|
|
/* Pseudo v-table */
|
|
void (*init)(struct state_machine_t *this);
|
|
void (*run)(struct state_machine_t *this,
|
|
enum state_machine_input_t input,
|
|
void *arg);
|
|
};
|
|
|
|
/* State Machine Member Function Prototypes */
|
|
static void state_machine_init(struct state_machine_t *this);
|
|
static void state_machine_run(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg);
|
|
|
|
/* State Machine States (nodes) */
|
|
/* If adding a state, be sure to also update state_machine_run() strings */
|
|
static void pacman_state_init(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg);
|
|
static void pacman_state_disabled(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg);
|
|
static void pacman_state_enabling(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg);
|
|
static void pacman_state_enabled(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg);
|
|
static void pacman_state_disabling(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg);
|
|
|
|
/* State Machine Member Functions */
|
|
static void state_machine_init(struct state_machine_t *this)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
memset(this, '0', sizeof(struct state_machine_t));
|
|
this->curr_state = pacman_state_init;
|
|
this->next_state = pacman_state_init;
|
|
this->curr_state_string = "INIT";
|
|
|
|
/* Setup Pseudo V-Table */
|
|
this->init = state_machine_init;
|
|
this->run = state_machine_run;
|
|
|
|
/* Run the state machine with the 'INIT' event */
|
|
this->run(this, INIT, NULL);
|
|
}
|
|
|
|
static void state_machine_run(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
void (*tmp_state)(struct state_machine_t *sm,
|
|
enum state_machine_input_t input,
|
|
void *arg);
|
|
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
if (this == NULL || this->curr_state == NULL)
|
|
return;
|
|
|
|
/* Execute the current state with the given input event */
|
|
(this->curr_state)(this, input, NULL);
|
|
|
|
/* If the current state set this->next_state, process the transition */
|
|
while (this->curr_state != this->next_state) {
|
|
if (NULL == this->next_state)
|
|
break;
|
|
|
|
tmp_state = this->next_state;
|
|
(this->next_state)(this, input, arg);
|
|
|
|
/* Transition complete - update curr_state */
|
|
this->curr_state = tmp_state;
|
|
if (this->curr_state == pacman_state_init)
|
|
this->curr_state_string = "STATE_INIT";
|
|
else if (this->curr_state == pacman_state_disabled)
|
|
this->curr_state_string = "STATE_DISABLED";
|
|
else if (this->curr_state == pacman_state_enabling)
|
|
this->curr_state_string = "STATE_ENABLING";
|
|
else if (this->curr_state == pacman_state_enabled)
|
|
this->curr_state_string = "STATE_ENABLED";
|
|
else if (this->curr_state == pacman_state_disabling)
|
|
this->curr_state_string = "STATE_DISABLING";
|
|
else
|
|
this->curr_state_string = "STATE_UNKNOWN";
|
|
}
|
|
}
|
|
|
|
/* QUP Framework (struct qup_instance passed as argument to state machine) */
|
|
enum subsystem {
|
|
NONE = 0,
|
|
APSS = 1,
|
|
ADSP = 2
|
|
};
|
|
|
|
const char *subsystem_strings[] = {
|
|
"NONE",
|
|
"APSS",
|
|
"ADSP"
|
|
};
|
|
|
|
struct qup_instance {
|
|
int id;
|
|
enum subsystem owner;
|
|
enum subsystem next_owner;
|
|
struct state_machine_t state_machine;
|
|
struct device *bus_dev;
|
|
struct device_driver *bus_drv;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* PACMan Framework
|
|
******************************************************************************/
|
|
static void pacman_framework_init(void);
|
|
static void pacman_run(char *command_string);
|
|
static void pacman_dump(char *buf, size_t length);
|
|
|
|
/* Globals */
|
|
static DEFINE_MUTEX(pacman_mutex);
|
|
static struct qup_instance QUP_TABLE[NUM_QUPS];
|
|
static int pacman_adsp_in_ssr;
|
|
|
|
static void pacman_framework_init(void)
|
|
{
|
|
int i;
|
|
|
|
pr_debug("%s: Number of QUPs = %i\n", __func__, NUM_QUPS);
|
|
|
|
/* Initialize Global QUP Table */
|
|
for (i = 0; i < NUM_QUPS; i++) {
|
|
QUP_TABLE[i].id = -1; /* -1 Invalid */
|
|
QUP_TABLE[i].owner = NONE;
|
|
QUP_TABLE[i].next_owner = NONE;
|
|
QUP_TABLE[i].bus_dev = NULL;
|
|
QUP_TABLE[i].bus_drv = NULL;
|
|
state_machine_init(&QUP_TABLE[i].state_machine);
|
|
}
|
|
|
|
mutex_init(&pacman_mutex);
|
|
}
|
|
|
|
static void pacman_run(char *command_string)
|
|
{
|
|
char buf[32], bus_string[16], subsystem_string[8];
|
|
char *str, *str_running;
|
|
const char delimiters[] = "\n";
|
|
struct device *bus_dev;
|
|
enum subsystem subsystem;
|
|
int qup_id;
|
|
|
|
mutex_lock(&pacman_mutex);
|
|
strlcpy(buf, command_string, sizeof(buf));
|
|
buf[31] = '\0';
|
|
str_running = (char *)&buf;
|
|
|
|
pr_debug("%s: %s\n", __func__, (char *)&buf);
|
|
|
|
/* Parse Bus ID */
|
|
str = strsep(&str_running, delimiters);
|
|
if (NULL == str) {
|
|
pr_err("%s: ERROR strsep expected bus name\n",
|
|
__func__);
|
|
goto error;
|
|
}
|
|
strlcpy(bus_string, str, sizeof(bus_string));
|
|
|
|
/* Parse Subsystem Name */
|
|
str = strsep(&str_running, delimiters);
|
|
if (NULL == str) {
|
|
pr_err("%s: ERROR strsep expected subsystem name\n",
|
|
__func__);
|
|
goto error;
|
|
}
|
|
strlcpy(subsystem_string, str, sizeof(subsystem_string));
|
|
|
|
/* Parse Extra Arguments */
|
|
str = strsep(&str_running, delimiters);
|
|
if (NULL != str) {
|
|
if (0 != strcmp("", str))
|
|
pr_debug("%s: Extra arguments are being ignored\n",
|
|
__func__);
|
|
}
|
|
|
|
/* Validate Bus Argument*/
|
|
bus_dev = bus_find_device_by_name(&platform_bus_type, NULL, bus_string);
|
|
if (!bus_dev) {
|
|
pr_err("%s: ERROR invalid bus\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Validate Bus to QUP ID mapping exists in device tree
|
|
* i.e. Device tree contains aliases{ qup5 = &i2c5; }
|
|
*/
|
|
qup_id = of_alias_get_id(bus_dev->of_node, "qup");
|
|
if (qup_id < 0) {
|
|
pr_err("%s: ERROR QUP ID not found\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
/* Validate Subsystem Argument*/
|
|
if (0 == strcmp("APSS", subsystem_string))
|
|
subsystem = APSS;
|
|
else if (0 == strcmp("ADSP", subsystem_string))
|
|
subsystem = ADSP;
|
|
else if (0 == strcmp("SSR", subsystem_string))
|
|
subsystem = NONE;
|
|
else {
|
|
pr_err("%s: ERROR invalid subsystem\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
pr_debug("%s: Bus=%s QUP=%i Subsystem=%s\n", __func__,
|
|
bus_string, qup_id, subsystem_string);
|
|
|
|
/* Flag QUP as valid upon first PACman invocation */
|
|
QUP_TABLE[qup_id].id = qup_id;
|
|
QUP_TABLE[qup_id].bus_dev = bus_dev;
|
|
/* Prepare for state machine */
|
|
QUP_TABLE[qup_id].next_owner = subsystem;
|
|
|
|
/* Invoke state machine transition */
|
|
switch (subsystem) {
|
|
case APSS:
|
|
QUP_TABLE[qup_id].state_machine.run(
|
|
&QUP_TABLE[qup_id].state_machine,
|
|
ENABLE, &QUP_TABLE[qup_id]);
|
|
if (QUP_TABLE[qup_id].state_machine.curr_state ==
|
|
pacman_state_enabled)
|
|
QUP_TABLE[qup_id].owner = APSS;
|
|
else
|
|
QUP_TABLE[qup_id].owner = NONE;
|
|
break;
|
|
case ADSP:
|
|
QUP_TABLE[qup_id].state_machine.run(
|
|
&QUP_TABLE[qup_id].state_machine,
|
|
DISABLE, &QUP_TABLE[qup_id]);
|
|
if (QUP_TABLE[qup_id].state_machine.curr_state ==
|
|
pacman_state_disabled)
|
|
QUP_TABLE[qup_id].owner = ADSP;
|
|
else
|
|
QUP_TABLE[qup_id].owner = NONE;
|
|
break;
|
|
case NONE: /* SSR */
|
|
pr_debug("%s: SSR initiated on QUP %i\n", __func__, qup_id);
|
|
QUP_TABLE[qup_id].state_machine.run(
|
|
&QUP_TABLE[qup_id].state_machine,
|
|
SSR, &QUP_TABLE[qup_id]);
|
|
|
|
/*
|
|
* TODO: Notify client of SSR event via poll() implementation
|
|
* Currently ownership is passed back after SSR
|
|
* Do not update the table owner for now - used to detect SSR
|
|
*/
|
|
break;
|
|
default:
|
|
pr_err("%s: ERROR owner not supported\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
mutex_unlock(&pacman_mutex);
|
|
}
|
|
|
|
static void pacman_dump(char *buf, size_t length)
|
|
{
|
|
char working_buf[32];
|
|
int rc, i;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (buf == NULL || length < 256) {
|
|
pr_debug("%s: ERROR input buffer too small\n", __func__);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(&pacman_mutex);
|
|
|
|
/* Parse QUP table and dump state*/
|
|
buf[0] = '\0';
|
|
|
|
for (i = 0; i < NUM_QUPS; i++) {
|
|
/* Check if enabled for dumping */
|
|
if (QUP_TABLE[i].id >= 0) {
|
|
rc = snprintf((char *)(&working_buf),
|
|
sizeof(working_buf),
|
|
"%u %s %s\n", QUP_TABLE[i].id,
|
|
subsystem_strings[QUP_TABLE[i].owner],
|
|
QUP_TABLE[i].state_machine.curr_state_string);
|
|
strlcat(buf, (char *)&working_buf, length);
|
|
/* Check for overflow */
|
|
if ((length - strlen(buf) - 1) <
|
|
sizeof(working_buf)) {
|
|
pr_debug("%s Overflow Detected\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&pacman_mutex);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* QMI Framework
|
|
******************************************************************************/
|
|
/* Configuration */
|
|
#define PACMAN_QMI_TIMEOUT_MS 5000
|
|
#define PACMAN_QMI_ADSP_SERVICE_ID 771
|
|
#define PACMAN_QMI_ADSP_SERVICE_VERSION 1
|
|
#define PACMAN_QMI_ADSP_SERVICE_INSTANCE_ID 1
|
|
|
|
/* QMI Client Port Handles */
|
|
static struct qmi_handle *pacman_qmi_client_port_adsp;
|
|
|
|
/*
|
|
* TODO: Scale worker threads for other subsystems by passing the QMI
|
|
* port handle from the callback function to the worker thread
|
|
*/
|
|
/* QMI Worker Threads */
|
|
static void pacman_qmi_client_notify_arrive_worker(struct work_struct *work);
|
|
static DECLARE_DELAYED_WORK(work_qmi_client_notify_arrive,
|
|
pacman_qmi_client_notify_arrive_worker);
|
|
static void pacman_qmi_client_notify_exit_worker(struct work_struct *work);
|
|
static DECLARE_DELAYED_WORK(work_qmi_client_notify_exit,
|
|
pacman_qmi_client_notify_exit_worker);
|
|
static void pacman_qmi_client_port_notify_worker(struct work_struct *work);
|
|
static DECLARE_DELAYED_WORK(work_qmi_client_port_notify,
|
|
pacman_qmi_client_port_notify_worker);
|
|
static struct workqueue_struct *pacman_qmi_notification_workqueue;
|
|
static struct workqueue_struct *pacman_qmi_rx_workqueue;
|
|
|
|
/* QMI Callbacks */
|
|
static int pacman_qmi_client_notify_cb(struct notifier_block *this,
|
|
unsigned long code, void *cmd)
|
|
{
|
|
int rc;
|
|
|
|
switch (code) {
|
|
case QMI_SERVER_ARRIVE:
|
|
pr_debug("%s: QMI_SERVER_ARRIVE\n", __func__);
|
|
rc = queue_delayed_work(pacman_qmi_notification_workqueue,
|
|
&work_qmi_client_notify_arrive, 0);
|
|
if (rc != 1)
|
|
pr_err("%s ERROR QMI packet dropped\n", __func__);
|
|
break;
|
|
case QMI_SERVER_EXIT:
|
|
pr_debug("%s: QMI_SERVER_EXIT\n", __func__);
|
|
rc = queue_delayed_work(pacman_qmi_notification_workqueue,
|
|
&work_qmi_client_notify_exit, 0);
|
|
if (rc != 1)
|
|
pr_err("%s ERROR QMI packet dropped\n", __func__);
|
|
break;
|
|
default:
|
|
pr_err("%s: Unknown code %u\n", __func__, (unsigned)code);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pacman_qmi_client_port_notify_cb(struct qmi_handle *handle,
|
|
enum qmi_event_type event,
|
|
void *notify_priv)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
switch (event) {
|
|
case QMI_RECV_MSG:
|
|
rc = queue_delayed_work(pacman_qmi_rx_workqueue,
|
|
&work_qmi_client_port_notify, 0);
|
|
if (rc != 1)
|
|
pr_err("%s ERROR QMI packet dropped\n", __func__);
|
|
break;
|
|
default:
|
|
pr_err("%s: Unknown event %i\n", __func__, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* QMI Worker Threads */
|
|
static void pacman_qmi_client_notify_arrive_worker(struct work_struct *w_s)
|
|
{
|
|
int rc, i;
|
|
char buff[32];
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
/* Create a Local client port for QMI communication */
|
|
pacman_qmi_client_port_adsp = qmi_handle_create(
|
|
pacman_qmi_client_port_notify_cb, NULL);
|
|
if (!pacman_qmi_client_port_adsp) {
|
|
pr_err("%s: qmi_handle_create failed\n", __func__);
|
|
return;
|
|
}
|
|
|
|
rc = qmi_connect_to_service(pacman_qmi_client_port_adsp,
|
|
PACMAN_QMI_ADSP_SERVICE_ID,
|
|
PACMAN_QMI_ADSP_SERVICE_VERSION,
|
|
PACMAN_QMI_ADSP_SERVICE_INSTANCE_ID);
|
|
|
|
if (rc < 0) {
|
|
pr_err("%s: qmi_connect_to_service failed rc=%i\n",
|
|
__func__, rc);
|
|
qmi_handle_destroy(pacman_qmi_client_port_adsp);
|
|
pacman_qmi_client_port_adsp = NULL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Re-establish ADSP ownership if SSR occurred
|
|
* Note: Notification work queue serializes use of
|
|
* IN_SSR flag and SSR
|
|
*/
|
|
if (pacman_adsp_in_ssr) {
|
|
for (i = 0; i < NUM_QUPS; i++) {
|
|
if (QUP_TABLE[i].owner == ADSP) {
|
|
snprintf((char *)&buff, sizeof(buff),
|
|
"%i ADSP", i);
|
|
msleep(2000); /* Wait for ADSP to be fully up */
|
|
pacman_run((char *)&buff);
|
|
}
|
|
}
|
|
|
|
pacman_adsp_in_ssr = 0;
|
|
}
|
|
}
|
|
|
|
static void pacman_qmi_client_notify_exit_worker(struct work_struct *w_s)
|
|
{
|
|
int i;
|
|
char buff[12];
|
|
|
|
pr_debug("%s\n", __func__);
|
|
pacman_adsp_in_ssr = 1;
|
|
|
|
/* Inject a SSR Event into PACMan for each registered QUP */
|
|
for (i = 0; i < NUM_QUPS; i++) {
|
|
if (QUP_TABLE[i].owner == ADSP) {
|
|
snprintf((char *)&buff, sizeof(buff), "%i SSR", i);
|
|
pacman_run((char *)&buff);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pacman_qmi_client_port_notify_worker(struct work_struct *work)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
do {
|
|
rc = qmi_recv_msg(pacman_qmi_client_port_adsp);
|
|
} while (0 == rc);
|
|
|
|
if (rc != -ENOMSG)
|
|
pr_err("%s: Error receiving QMI message\n", __func__);
|
|
}
|
|
|
|
static struct notifier_block pacman_qmi_notifier_block = {
|
|
.notifier_call = pacman_qmi_client_notify_cb,
|
|
};
|
|
|
|
static int pacman_qmi_init(void)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
pacman_qmi_client_port_adsp = NULL;
|
|
pacman_qmi_notification_workqueue =
|
|
create_singlethread_workqueue("pacman_qmi_notification_wq");
|
|
if (!pacman_qmi_notification_workqueue) {
|
|
pr_err("%s: pacman_qmi_notification_wq creation failed\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
pacman_qmi_rx_workqueue =
|
|
create_singlethread_workqueue("pacman_qmi_rx_wq");
|
|
if (!pacman_qmi_rx_workqueue) {
|
|
pr_err("%s: pacman_qmi_rx_wq creation failed\n",
|
|
__func__);
|
|
destroy_workqueue(pacman_qmi_notification_workqueue);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* Register for ADSP PACMan Service */
|
|
rc = qmi_svc_event_notifier_register(PACMAN_QMI_ADSP_SERVICE_ID,
|
|
PACMAN_QMI_ADSP_SERVICE_VERSION,
|
|
PACMAN_QMI_ADSP_SERVICE_INSTANCE_ID,
|
|
&pacman_qmi_notifier_block);
|
|
if (rc < 0) {
|
|
pr_err("%s: qmi_svc_event_notifier_register failed rc=%i\n",
|
|
__func__, rc);
|
|
destroy_workqueue(pacman_qmi_notification_workqueue);
|
|
destroy_workqueue(pacman_qmi_rx_workqueue);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pacman_qmi_deinit(void)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
|
|
/* Unregister for ADSP PACMan Service */
|
|
qmi_handle_destroy(pacman_qmi_client_port_adsp);
|
|
pacman_qmi_client_port_adsp = NULL;
|
|
qmi_svc_event_notifier_unregister(PACMAN_QMI_ADSP_SERVICE_ID,
|
|
PACMAN_QMI_ADSP_SERVICE_VERSION,
|
|
PACMAN_QMI_ADSP_SERVICE_INSTANCE_ID,
|
|
&pacman_qmi_notifier_block);
|
|
|
|
if (pacman_qmi_notification_workqueue) {
|
|
flush_workqueue(pacman_qmi_notification_workqueue);
|
|
destroy_workqueue(pacman_qmi_notification_workqueue);
|
|
}
|
|
if (pacman_qmi_rx_workqueue) {
|
|
flush_workqueue(pacman_qmi_rx_workqueue);
|
|
destroy_workqueue(pacman_qmi_rx_workqueue);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_BYPASS_QMI
|
|
static int pacman_qmi_send_sync_ready_msg(struct qmi_handle *qmi_port_handle,
|
|
int qup_id)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
static int pacman_qmi_send_sync_ready_msg(struct qmi_handle *qmi_port_handle,
|
|
int qup_id)
|
|
{
|
|
struct qupm_ready_req_msg_v01 req_msg;
|
|
struct qupm_ready_resp_msg_v01 resp_msg;
|
|
struct msg_desc req_desc, resp_desc;
|
|
int rc;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (qmi_port_handle == NULL || qup_id < 0 || qup_id >= NUM_QUPS) {
|
|
pr_err("%s: invalid arguments\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
req_desc.max_msg_len = QUPM_READY_REQ_MSG_V01_MAX_MSG_LEN;
|
|
req_desc.msg_id = QMI_QUPM_READY_REQ_V01;
|
|
req_desc.ei_array = qupm_ready_req_msg_v01_ei;
|
|
req_msg.qup_id = qup_id;
|
|
req_msg.flags_valid = 0;
|
|
req_msg.flags = 0;
|
|
|
|
resp_desc.max_msg_len = QUPM_READY_RESP_MSG_V01_MAX_MSG_LEN;
|
|
resp_desc.msg_id = QMI_QUPM_READY_RESP_V01;
|
|
resp_desc.ei_array = qupm_ready_resp_msg_v01_ei;
|
|
|
|
rc = qmi_send_req_wait(qmi_port_handle, &req_desc,
|
|
&req_msg, sizeof(req_msg),
|
|
&resp_desc, &resp_msg, sizeof(resp_msg),
|
|
PACMAN_QMI_TIMEOUT_MS);
|
|
if (rc < 0) {
|
|
pr_err("%s: qmi_send_req_wait failed rc=%i\n", __func__, rc);
|
|
goto error;
|
|
}
|
|
|
|
pr_debug("%s: QMI Response result=%hu error=%hu\n",
|
|
__func__, resp_msg.resp.result, resp_msg.resp.error);
|
|
|
|
return (uint16_t)resp_msg.resp.result;
|
|
|
|
error:
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_BYPASS_QMI
|
|
static int pacman_qmi_send_sync_take_ownership_msg(
|
|
struct qmi_handle *qmi_port_handle,
|
|
int qup_id)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
static int pacman_qmi_send_sync_take_ownership_msg(
|
|
struct qmi_handle *qmi_port_handle,
|
|
int qup_id)
|
|
{
|
|
struct qupm_take_ownership_req_msg_v01 req_msg;
|
|
struct qupm_take_ownership_resp_msg_v01 resp_msg;
|
|
struct msg_desc req_desc, resp_desc;
|
|
int rc;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (NULL == qmi_port_handle || qup_id < 0 || qup_id >= NUM_QUPS) {
|
|
pr_err("%s: invalid arguments\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
req_desc.max_msg_len = QUPM_TAKE_OWNERSHIP_REQ_MSG_V01_MAX_MSG_LEN;
|
|
req_desc.msg_id = QMI_QUPM_TAKE_OWNERSHIP_REQ_V01;
|
|
req_desc.ei_array = qupm_take_ownership_req_msg_v01_ei;
|
|
req_msg.qup_id = qup_id;
|
|
req_msg.flags_valid = 0;
|
|
req_msg.flags = 0;
|
|
|
|
resp_desc.max_msg_len = QUPM_TAKE_OWNERSHIP_RESP_MSG_V01_MAX_MSG_LEN;
|
|
resp_desc.msg_id = QMI_QUPM_TAKE_OWNERSHIP_RESP_V01;
|
|
resp_desc.ei_array = qupm_take_ownership_resp_msg_v01_ei;
|
|
|
|
rc = qmi_send_req_wait(qmi_port_handle, &req_desc,
|
|
&req_msg, sizeof(req_msg),
|
|
&resp_desc, &resp_msg, sizeof(resp_msg),
|
|
PACMAN_QMI_TIMEOUT_MS);
|
|
if (rc < 0) {
|
|
pr_err("%s: qmi_send_req_wait failed rc=%i\n", __func__, rc);
|
|
goto error;
|
|
}
|
|
|
|
pr_debug("%s: QMI Response result=%hu error=%hu\n",
|
|
__func__, resp_msg.resp.result, resp_msg.resp.error);
|
|
|
|
return (uint16_t)resp_msg.resp.result;
|
|
|
|
error:
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef DEBUG_BYPASS_QMI
|
|
static int pacman_qmi_send_sync_give_ownership_msg(
|
|
struct qmi_handle *qmi_port_handle,
|
|
int qup_id)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
static int pacman_qmi_send_sync_give_ownership_msg(
|
|
struct qmi_handle *qmi_port_handle,
|
|
int qup_id)
|
|
{
|
|
struct qupm_give_ownership_req_msg_v01 req_msg;
|
|
struct qupm_give_ownership_resp_msg_v01 resp_msg;
|
|
struct msg_desc req_desc, resp_desc;
|
|
int rc;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (NULL == qmi_port_handle || qup_id < 0 || qup_id >= NUM_QUPS) {
|
|
pr_err("%s: invalid arguments\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
req_desc.max_msg_len = QUPM_GIVE_OWNERSHIP_REQ_MSG_V01_MAX_MSG_LEN;
|
|
req_desc.msg_id = QMI_QUPM_GIVE_OWNERSHIP_REQ_V01;
|
|
req_desc.ei_array = qupm_give_ownership_req_msg_v01_ei;
|
|
req_msg.qup_id = qup_id;
|
|
req_msg.flags_valid = 0;
|
|
req_msg.flags = 0;
|
|
|
|
resp_desc.max_msg_len = QUPM_GIVE_OWNERSHIP_RESP_MSG_V01_MAX_MSG_LEN;
|
|
resp_desc.msg_id = QMI_QUPM_GIVE_OWNERSHIP_RESP_V01;
|
|
resp_desc.ei_array = qupm_give_ownership_resp_msg_v01_ei;
|
|
|
|
rc = qmi_send_req_wait(qmi_port_handle, &req_desc,
|
|
&req_msg, sizeof(req_msg),
|
|
&resp_desc, &resp_msg, sizeof(resp_msg),
|
|
PACMAN_QMI_TIMEOUT_MS);
|
|
if (rc < 0) {
|
|
pr_err("%s: qmi_send_req_wait failed rc=%i\n", __func__, rc);
|
|
goto error;
|
|
}
|
|
|
|
pr_debug("%s: QMI Response result=%hu error=%hu\n",
|
|
__func__, resp_msg.resp.result, resp_msg.resp.error);
|
|
|
|
return (uint16_t)resp_msg.resp.result;
|
|
|
|
error:
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
/******************************************************************************
|
|
* State Machine States
|
|
******************************************************************************/
|
|
static void pacman_state_init(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
|
|
/* Verify expected events while in state */
|
|
if (input != INIT || NULL == this->curr_state) {
|
|
pr_err("%s: ERROR with input %u\n", __func__, input);
|
|
return;
|
|
}
|
|
|
|
/* For PACMan running on APSS, we want the default
|
|
* state to be enabled
|
|
*/
|
|
if (input == INIT) {
|
|
this->next_state = pacman_state_enabled;
|
|
return;
|
|
}
|
|
|
|
this->next_state = pacman_state_disabled;
|
|
}
|
|
|
|
static void pacman_state_disabled(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
|
|
/* Verify expected events while in state */
|
|
if (input != INIT && input != DISABLE && input != ENABLE &&
|
|
input != SSR) {
|
|
pr_err("%s: ERROR with input %u\n", __func__, input);
|
|
return;
|
|
}
|
|
|
|
/* Check what state we transitioned from */
|
|
if (this->curr_state == pacman_state_enabling) {
|
|
pr_err("%s: pacman_state_enabling failed to execute\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
if (input == ENABLE || input == SSR)
|
|
this->next_state = pacman_state_enabling;
|
|
}
|
|
|
|
static void pacman_state_enabled(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
|
|
/* Verify expected events while in state */
|
|
if (input != INIT && input != DISABLE && input != ENABLE &&
|
|
input != SSR) {
|
|
pr_err("%s: ERROR with input %u\n", __func__, input);
|
|
return;
|
|
}
|
|
|
|
/* Check what state we transitioned from */
|
|
if (this->curr_state == pacman_state_disabling) {
|
|
pr_err("%s: pacman_state_disabling failed to execute\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
if (input == DISABLE)
|
|
this->next_state = pacman_state_disabling;
|
|
}
|
|
|
|
#ifndef DEBUG_BYPASS_BINDING
|
|
static void pacman_state_enabling(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
int rc;
|
|
struct qup_instance *qup_instance = arg;
|
|
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
|
|
/* Verify expected events while in state */
|
|
if (input != ENABLE && input != SSR) {
|
|
pr_err("%s: ERROR with input %u\n", __func__, input);
|
|
goto general_error;
|
|
}
|
|
|
|
/* Verify inputs */
|
|
if (NULL == qup_instance) {
|
|
pr_err("%s: ERROR QUP configuration invalid\n",
|
|
__func__);
|
|
goto general_error;
|
|
}
|
|
|
|
pr_debug("%s: QUP ID=%i Subsystem=%s\n", __func__,
|
|
qup_instance->id,
|
|
subsystem_strings[qup_instance->next_owner]);
|
|
|
|
/* Do not attempt to communicate with ADSP if we have a SSR event */
|
|
if (input == SSR)
|
|
goto ssr_event;
|
|
|
|
/* APSS should be the requested owner */
|
|
if (qup_instance->next_owner != APSS) {
|
|
pr_err("%s: ERROR support for APSS Only\n", __func__);
|
|
goto general_error;
|
|
}
|
|
/* Check if ADSP is OK to release ownership */
|
|
rc = pacman_qmi_send_sync_ready_msg(pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR pacman_qmi_send_sync_ready_msg %i\n",
|
|
__func__, rc);
|
|
goto general_error;
|
|
}
|
|
/* APSS needs to be given ownership */
|
|
rc = pacman_qmi_send_sync_give_ownership_msg(
|
|
pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR send_sync_give_ownership_msg %i\n",
|
|
__func__, rc);
|
|
goto general_error;
|
|
}
|
|
ssr_event:
|
|
/* Call into TrustZone */
|
|
rc = tz_configure_blsp_ownership(qup_instance->id, APSS);
|
|
if (rc) {
|
|
pr_err("%s: ERROR calling into TZ %i\n", __func__, rc);
|
|
goto ownership_cleanup;
|
|
}
|
|
/* Enable the bus and all its peripherals by binding the bus driver */
|
|
|
|
rc = driver_attach(qup_instance->bus_drv);
|
|
if (rc) {
|
|
pr_err("%s: ERROR calling driver_enable %i\n",
|
|
__func__, rc);
|
|
goto tz_cleanup;
|
|
}
|
|
/* State transition successful */
|
|
this->next_state = pacman_state_enabled;
|
|
return;
|
|
|
|
/* APSS is master, so the following is a true critical error */
|
|
tz_cleanup:
|
|
ownership_cleanup:
|
|
general_error:
|
|
pr_err("%s: CRITICAL ERROR\n", __func__);
|
|
}
|
|
|
|
static void pacman_state_disabling(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
int rc;
|
|
struct qup_instance *qup_instance = arg;
|
|
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
|
|
/* Verify expected events while in state */
|
|
if (input != DISABLE) {
|
|
pr_err("%s: ERROR with input %u\n", __func__, input);
|
|
goto general_error;
|
|
}
|
|
|
|
/* Verify inputs */
|
|
if (NULL == qup_instance) {
|
|
pr_err("%s: ERROR - QUP configuration invalid\n",
|
|
__func__);
|
|
goto general_error;
|
|
}
|
|
|
|
pr_debug("%s: QUP ID=%i Subsystem=%s\n", __func__,
|
|
qup_instance->id,
|
|
subsystem_strings[qup_instance->next_owner]);
|
|
|
|
/* ADSP should be the owner */
|
|
if (qup_instance->next_owner != ADSP) {
|
|
pr_err("%s: ERROR Support for ADSP Only\n", __func__);
|
|
goto general_error;
|
|
}
|
|
/* Check if ADSP is OK to take ownership */
|
|
rc = pacman_qmi_send_sync_ready_msg(pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR pacman_qmi_send_sync_ready_msg %i\n",
|
|
__func__, rc);
|
|
goto general_error;
|
|
}
|
|
/* Save the current bus device->device_driver for restoring later */
|
|
qup_instance->bus_drv = qup_instance->bus_dev->driver;
|
|
/* Disable the bus and peripherals by unbindng the bus */
|
|
|
|
pr_debug("%s: Unbinding the bus\n", __func__);
|
|
device_release_driver(qup_instance->bus_dev);
|
|
/*
|
|
* Unfortunately no return code to check here for a successful unbind.
|
|
* If a peripheral does not unbind successfully, it may hang here
|
|
*/
|
|
pr_debug("%s: Unbinding the bus complete\n", __func__);
|
|
|
|
/* Call into TrustZone */
|
|
rc = tz_configure_blsp_ownership(qup_instance->id,
|
|
qup_instance->next_owner);
|
|
if (rc) {
|
|
pr_err("%s: ERROR calling into TZ %i\n", __func__, rc);
|
|
goto driver_disable_cleanup;
|
|
}
|
|
/* Transfer ownership to subsystem */
|
|
rc = pacman_qmi_send_sync_take_ownership_msg(
|
|
pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR send_sync_take_ownership_msg %i\n",
|
|
__func__, rc);
|
|
goto tz_cleanup;
|
|
}
|
|
/* State transition successful */
|
|
this->next_state = pacman_state_disabled;
|
|
return;
|
|
|
|
tz_cleanup:
|
|
rc = tz_configure_blsp_ownership(qup_instance->id, APSS);
|
|
if (rc)
|
|
pr_err("%s: CRITICAL ERROR calling into TZ %i\n",
|
|
__func__, rc);
|
|
driver_disable_cleanup:
|
|
rc = driver_attach(qup_instance->bus_drv);
|
|
if (rc)
|
|
pr_err("%s: CRITICAL ERROR driver_enable %i\n",
|
|
__func__, rc);
|
|
general_error:
|
|
this->next_state = pacman_state_enabled;
|
|
}
|
|
#else
|
|
static void pacman_state_enabling(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
int rc;
|
|
struct qup_instance *qup_instance = arg;
|
|
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
|
|
/* Verify expected events while in state */
|
|
if (input != ENABLE && input != SSR) {
|
|
pr_err("%s: ERROR with input %u\n", __func__, input);
|
|
goto general_error;
|
|
}
|
|
|
|
/* Verify inputs */
|
|
if (NULL == qup_instance) {
|
|
pr_err("%s: ERROR QUP configuration invalid\n",
|
|
__func__);
|
|
goto general_error;
|
|
}
|
|
|
|
pr_debug("%s: QUP ID=%i Subsystem=%s\n", __func__,
|
|
qup_instance->id,
|
|
subsystem_strings[qup_instance->next_owner]);
|
|
|
|
/* Do not attempt to communicate with ADSP if we have a SSR event */
|
|
if (input == SSR)
|
|
goto ssr_event;
|
|
|
|
/* APSS should be the requested owner */
|
|
if (qup_instance->next_owner != APSS) {
|
|
pr_err("%s: ERROR support for APSS Only\n", __func__);
|
|
goto general_error;
|
|
}
|
|
/* Check if ADSP is OK to release ownership */
|
|
rc = pacman_qmi_send_sync_ready_msg(pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR pacman_qmi_send_sync_ready_msg %i\n",
|
|
__func__, rc);
|
|
goto general_error;
|
|
}
|
|
/* APSS needs to be given ownership */
|
|
rc = pacman_qmi_send_sync_give_ownership_msg(
|
|
pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR send_sync_give_ownership_msg %i\n",
|
|
__func__, rc);
|
|
goto general_error;
|
|
}
|
|
ssr_event:
|
|
/* Call into TrustZone */
|
|
rc = tz_configure_blsp_ownership(qup_instance->id, APSS);
|
|
if (rc) {
|
|
pr_err("%s: ERROR calling into TZ %i\n", __func__, rc);
|
|
goto ownership_cleanup;
|
|
}
|
|
|
|
/* State transition successful */
|
|
this->next_state = pacman_state_enabled;
|
|
return;
|
|
|
|
/* APSS is master, so the following is a true critical error */
|
|
tz_cleanup:
|
|
ownership_cleanup:
|
|
general_error:
|
|
pr_err("%s: CRITICAL ERROR\n", __func__);
|
|
}
|
|
|
|
static void pacman_state_disabling(struct state_machine_t *this,
|
|
enum state_machine_input_t input, void *arg)
|
|
{
|
|
int rc;
|
|
struct qup_instance *qup_instance = arg;
|
|
|
|
pr_debug("%s: input %u\n", __func__, input);
|
|
|
|
/* Verify expected events while in state */
|
|
if (input != DISABLE) {
|
|
pr_err("%s: ERROR with input %u\n", __func__, input);
|
|
goto general_error;
|
|
}
|
|
|
|
/* Verify inputs */
|
|
if (NULL == qup_instance) {
|
|
pr_err("%s: ERROR - QUP configuration invalid\n",
|
|
__func__);
|
|
goto general_error;
|
|
}
|
|
|
|
pr_debug("%s: QUP ID=%i Subsystem=%s\n", __func__,
|
|
qup_instance->id,
|
|
subsystem_strings[qup_instance->next_owner]);
|
|
|
|
/* ADSP should be the owner */
|
|
if (qup_instance->next_owner != ADSP) {
|
|
pr_err("%s: ERROR Support for ADSP Only\n", __func__);
|
|
goto general_error;
|
|
}
|
|
/* Check if ADSP is OK to take ownership */
|
|
rc = pacman_qmi_send_sync_ready_msg(pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR pacman_qmi_send_sync_ready_msg %i\n",
|
|
__func__, rc);
|
|
goto general_error;
|
|
}
|
|
/* Save the current bus device->device_driver for restoring later */
|
|
qup_instance->bus_drv = qup_instance->bus_dev->driver;
|
|
|
|
/* Call into TrustZone */
|
|
rc = tz_configure_blsp_ownership(qup_instance->id,
|
|
qup_instance->next_owner);
|
|
if (rc) {
|
|
pr_err("%s: ERROR calling into TZ %i\n", __func__, rc);
|
|
goto driver_disable_cleanup;
|
|
}
|
|
/* Transfer ownership to subsystem */
|
|
rc = pacman_qmi_send_sync_take_ownership_msg(
|
|
pacman_qmi_client_port_adsp,
|
|
qup_instance->id);
|
|
if (rc) {
|
|
pr_err("%s: ERROR send_sync_take_ownership_msg %i\n",
|
|
__func__, rc);
|
|
goto tz_cleanup;
|
|
}
|
|
/* State transition successful */
|
|
this->next_state = pacman_state_disabled;
|
|
return;
|
|
|
|
tz_cleanup:
|
|
rc = tz_configure_blsp_ownership(qup_instance->id, APSS);
|
|
if (rc)
|
|
pr_err("%s: CRITICAL ERROR calling into TZ %i\n",
|
|
__func__, rc);
|
|
driver_disable_cleanup:
|
|
general_error:
|
|
this->next_state = pacman_state_enabled;
|
|
}
|
|
#endif
|
|
|
|
static int pacman_open(struct inode *ip, struct file *fp)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int pacman_close(struct inode *ip, struct file *fp)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t pacman_read(struct file *fp, char __user *buffer,
|
|
size_t length, loff_t *offset)
|
|
{
|
|
char buf[READ_BUF_SZ];
|
|
int rc;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
if (*offset == 0) {
|
|
*offset = 1;
|
|
pacman_dump((char *)&buf, sizeof(buf));
|
|
if (strlen(buf) < length) {
|
|
rc = copy_to_user(buffer, buf, strlen(buf));
|
|
pr_debug("%s:\n%s\n", __func__, (char *)&buf);
|
|
}
|
|
return strlen(buf);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static ssize_t pacman_write(struct file *fp, const char *buffer,
|
|
size_t length, loff_t *offset)
|
|
{
|
|
char buf[WRITE_BUF_SZ];
|
|
|
|
pr_debug("%s\n", __func__);
|
|
if (copy_from_user(buf, buffer, min(length, sizeof(buf))))
|
|
return -EFAULT;
|
|
buf[WRITE_BUF_SZ-1] = '\0';
|
|
pacman_run((char *)&buf);
|
|
return length;
|
|
}
|
|
|
|
const struct file_operations pacman_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = pacman_open,
|
|
.release = pacman_close,
|
|
.read = pacman_read,
|
|
.write = pacman_write
|
|
};
|
|
|
|
static int pacman_probe(struct platform_device *pdev)
|
|
{
|
|
pr_debug("%s: %s version %s\n", __func__, DEVICE_NAME, PACMAN_VERSION);
|
|
|
|
pacman_ctl.dev_num = register_chrdev(0, DEVICE_NAME, &pacman_fops);
|
|
if (pacman_ctl.dev_num < 0) {
|
|
pr_err("%s: register_chrdev failed\n", __func__);
|
|
goto register_chrdev_err;
|
|
}
|
|
|
|
pacman_ctl.dev_class = class_create(THIS_MODULE, CLASS_NAME);
|
|
if (IS_ERR(pacman_ctl.dev_class)) {
|
|
pr_err("%s: class_create failed\n", __func__);
|
|
goto class_create_err;
|
|
}
|
|
|
|
pacman_ctl.dev = device_create(pacman_ctl.dev_class, NULL,
|
|
MKDEV(pacman_ctl.dev_num, 0),
|
|
&pacman_ctl, DEVICE_NAME);
|
|
if (IS_ERR(pacman_ctl.dev)) {
|
|
pr_err("%s: device_create failed\n", __func__);
|
|
goto device_create_err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
device_create_err:
|
|
class_destroy(pacman_ctl.dev_class);
|
|
class_create_err:
|
|
unregister_chrdev(pacman_ctl.dev_num, DEVICE_NAME);
|
|
register_chrdev_err:
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int pacman_remove(struct platform_device *pdev)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
device_unregister(pacman_ctl.dev);
|
|
device_destroy(pacman_ctl.dev_class, MKDEV(pacman_ctl.dev_num, 0));
|
|
class_destroy(pacman_ctl.dev_class);
|
|
unregister_chrdev(pacman_ctl.dev_num, DEVICE_NAME);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id pacman_dt_match[] = {
|
|
{.compatible = "qcom,msm-pacman"},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, pacman_dt_match);
|
|
|
|
static struct platform_driver pacman_driver = {
|
|
.driver = {
|
|
.name = DEVICE_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = pacman_dt_match,
|
|
},
|
|
.probe = pacman_probe,
|
|
.remove = pacman_remove,
|
|
};
|
|
|
|
static int __init pacman_init(void)
|
|
{
|
|
int rc = 0;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
rc = platform_driver_register(&pacman_driver);
|
|
if (rc) {
|
|
pr_err("%s: ERROR Failed to register driver\n", __func__);
|
|
return rc;
|
|
}
|
|
pacman_framework_init();
|
|
rc = pacman_qmi_init();
|
|
if (rc) {
|
|
pr_err("%s: ERROR pacman_qmi_init failed\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit pacman_exit(void)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
pacman_qmi_deinit();
|
|
platform_driver_unregister(&pacman_driver);
|
|
}
|
|
|
|
MODULE_DESCRIPTION("Peripheral Access Control Manager (PACMan)");
|
|
MODULE_LICENSE("GPL v2");
|
|
module_init(pacman_init);
|
|
module_exit(pacman_exit);
|