drivers/soc/qcom/icnss.c (1,301 lines of code) (raw):

/* Copyright (c) 2015-2016, 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/export.h> #include <linux/err.h> #include <linux/of.h> #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/interrupt.h> #include <linux/sched.h> #include <linux/dma-mapping.h> #include <linux/qmi_encdec.h> #include <soc/qcom/memory_dump.h> #include <soc/qcom/icnss.h> #include <soc/qcom/msm_qmi_interface.h> #include "wlan_firmware_service_v01.h" enum icnss_qmi_event_type { ICNSS_QMI_EVENT_SERVER_ARRIVE, ICNSS_QMI_EVENT_SERVER_EXIT, ICNSS_QMI_EVENT_FW_READY_IND, }; struct icnss_qmi_event { struct list_head list; enum icnss_qmi_event_type type; void *data; }; #define ICNSS_PANIC 1 #define WLFW_TIMEOUT_MS 3000 #define WLFW_SERVICE_INS_ID_V01 0 #define ICNSS_WLFW_QMI_CONNECTED BIT(0) #define ICNSS_FW_READY BIT(1) #define MAX_PROP_SIZE 32 #define MAX_VOLTAGE_LEVEL 2 #define VREG_ON 1 #define VREG_OFF 0 #define ICNSS_IS_WLFW_QMI_CONNECTED(_state) \ ((_state) & ICNSS_WLFW_QMI_CONNECTED) #define ICNSS_IS_FW_READY(_state) ((_state) & ICNSS_FW_READY) #ifdef ICNSS_PANIC #define ICNSS_ASSERT(_condition) do { \ if (!(_condition)) { \ pr_err("ICNSS ASSERT in %s Line %d\n", \ __func__, __LINE__); \ BUG_ON(1); \ } \ } while (0) #else #define ICNSS_ASSERT(_condition) do { \ if (!(_condition)) { \ pr_err("ICNSS ASSERT in %s Line %d\n", \ __func__, __LINE__); \ WARN_ON(1); \ } \ } while (0) #endif struct ce_irq_list { int irq; irqreturn_t (*handler)(int, void *); }; struct icnss_vreg_info { struct regulator *reg; const char *name; u32 nominal_min; u32 max_voltage; bool state; }; static struct { struct platform_device *pdev; struct icnss_driver_ops *ops; struct ce_irq_list ce_irq_list[ICNSS_MAX_IRQ_REGISTRATIONS]; struct icnss_vreg_info vreg_info; u32 ce_irqs[ICNSS_MAX_IRQ_REGISTRATIONS]; phys_addr_t mem_base_pa; void __iomem *mem_base_va; struct qmi_handle *wlfw_clnt; struct list_head qmi_event_list; spinlock_t qmi_event_lock; struct work_struct qmi_event_work; struct work_struct qmi_recv_msg_work; struct workqueue_struct *qmi_event_wq; phys_addr_t msa_pa; uint32_t msa_mem_size; void *msa_va; uint32_t state; struct wlfw_rf_chip_info_s_v01 chip_info; struct wlfw_rf_board_info_s_v01 board_info; struct wlfw_soc_info_s_v01 soc_info; struct wlfw_fw_version_info_s_v01 fw_version_info; u32 pwr_pin_result; u32 phy_io_pin_result; u32 rf_pin_result; struct icnss_mem_region_info icnss_mem_region[QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01]; bool skip_qmi; } *penv; static int icnss_qmi_event_post(enum icnss_qmi_event_type type, void *data) { struct icnss_qmi_event *event = NULL; unsigned long flags; int gfp = GFP_KERNEL; if (in_interrupt() || irqs_disabled()) gfp = GFP_ATOMIC; event = kzalloc(sizeof(*event), gfp); if (event == NULL) return -ENOMEM; event->type = type; event->data = data; spin_lock_irqsave(&penv->qmi_event_lock, flags); list_add_tail(&event->list, &penv->qmi_event_list); spin_unlock_irqrestore(&penv->qmi_event_lock, flags); queue_work(penv->qmi_event_wq, &penv->qmi_event_work); return 0; } static int icnss_qmi_pin_connect_result_ind(void *msg, unsigned int msg_len) { struct msg_desc ind_desc; struct wlfw_pin_connect_result_ind_msg_v01 ind_msg; int ret = 0; if (!penv || !penv->wlfw_clnt) { ret = -ENODEV; goto out; } ind_desc.msg_id = QMI_WLFW_PIN_CONNECT_RESULT_IND_V01; ind_desc.max_msg_len = WLFW_PIN_CONNECT_RESULT_IND_MSG_V01_MAX_MSG_LEN; ind_desc.ei_array = wlfw_pin_connect_result_ind_msg_v01_ei; ret = qmi_kernel_decode(&ind_desc, &ind_msg, msg, msg_len); if (ret < 0) { pr_err("%s: Failed to decode message!\n", __func__); goto out; } /* store pin result locally */ if (ind_msg.pwr_pin_result_valid) penv->pwr_pin_result = ind_msg.pwr_pin_result; if (ind_msg.phy_io_pin_result_valid) penv->phy_io_pin_result = ind_msg.phy_io_pin_result; if (ind_msg.rf_pin_result_valid) penv->rf_pin_result = ind_msg.rf_pin_result; pr_debug("%s: Pin connect Result: pwr_pin: 0x%x phy_io_pin: 0x%x rf_io_pin: 0x%x\n", __func__, ind_msg.pwr_pin_result, ind_msg.phy_io_pin_result, ind_msg.rf_pin_result); out: return ret; } static int icnss_vreg_on(struct icnss_vreg_info *vreg_info) { int ret = 0; if (!vreg_info->reg) { pr_err("%s: regulator is not initialized\n", __func__); return -ENOENT; } if (!vreg_info->max_voltage || !vreg_info->nominal_min) { pr_err("%s: %s invalid constraints specified\n", __func__, vreg_info->name); return -EINVAL; } ret = regulator_set_voltage(vreg_info->reg, vreg_info->nominal_min, vreg_info->max_voltage); if (ret < 0) { pr_err("%s: regulator_set_voltage failed for (%s). min_uV=%d,max_uV=%d,ret=%d\n", __func__, vreg_info->name, vreg_info->nominal_min, vreg_info->max_voltage, ret); return ret; } ret = regulator_enable(vreg_info->reg); if (ret < 0) { pr_err("%s: Fail to enable regulator (%s) ret=%d\n", __func__, vreg_info->name, ret); } return ret; } static int icnss_vreg_off(struct icnss_vreg_info *vreg_info) { int ret = 0; int min_uV = 0; if (!vreg_info->reg) { pr_err("%s: regulator is not initialized\n", __func__); return -ENOENT; } ret = regulator_disable(vreg_info->reg); if (ret < 0) { pr_err("%s: Fail to disable regulator (%s) ret=%d\n", __func__, vreg_info->name, ret); return ret; } ret = regulator_set_voltage(vreg_info->reg, min_uV, vreg_info->max_voltage); if (ret < 0) { pr_err("%s: regulator_set_voltage failed for (%s). min_uV=%d,max_uV=%d,ret=%d\n", __func__, vreg_info->name, min_uV, vreg_info->max_voltage, ret); } return ret; } static int icnss_vreg_set(bool state) { int ret = 0; struct icnss_vreg_info *vreg_info = &penv->vreg_info; if (vreg_info->state == state) { pr_debug("Already %s state is %s\n", vreg_info->name, state ? "enabled" : "disabled"); return ret; } if (state) ret = icnss_vreg_on(vreg_info); else ret = icnss_vreg_off(vreg_info); if (ret < 0) goto out; pr_debug("%s: %s is now %s\n", __func__, vreg_info->name, state ? "enabled" : "disabled"); vreg_info->state = state; out: return ret; } static int icnss_adrastea_power_on(void) { int ret = 0; ret = icnss_vreg_set(VREG_ON); if (ret < 0) { pr_err("%s: Failed to turn on voltagre regulator: %d\n", __func__, ret); goto out; } /* TZ API of power on adrastea */ out: return ret; } static int icnss_adrastea_power_off(void) { int ret = 0; ret = icnss_vreg_set(VREG_OFF); if (ret < 0) { pr_err("%s: Failed to turn off voltagre regulator: %d\n", __func__, ret); goto out; } /* TZ API of power off adrastea */ out: return ret; } static int wlfw_msa_mem_info_send_sync_msg(void) { int ret = 0; int i; struct wlfw_msa_info_req_msg_v01 req; struct wlfw_msa_info_resp_msg_v01 resp; struct msg_desc req_desc, resp_desc; if (!penv || !penv->wlfw_clnt) { ret = -ENODEV; goto out; } memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); req.msa_addr = penv->msa_pa; req.size = penv->msa_mem_size; req_desc.max_msg_len = WLFW_MSA_INFO_REQ_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = QMI_WLFW_MSA_INFO_REQ_V01; req_desc.ei_array = wlfw_msa_info_req_msg_v01_ei; resp_desc.max_msg_len = WLFW_MSA_INFO_RESP_MSG_V01_MAX_MSG_LEN; resp_desc.msg_id = QMI_WLFW_MSA_INFO_RESP_V01; resp_desc.ei_array = wlfw_msa_info_resp_msg_v01_ei; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { pr_err("%s: send req failed %d\n", __func__, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { pr_err("%s: QMI request failed %d %d\n", __func__, resp.resp.result, resp.resp.error); ret = resp.resp.result; goto out; } pr_debug("%s: Receive mem_region_info_len: %d\n", __func__, resp.mem_region_info_len); if (resp.mem_region_info_len > 2) { pr_err("%s : Invalid memory region length received\n", __func__); ret = -EINVAL; goto out; } for (i = 0; i < resp.mem_region_info_len; i++) { penv->icnss_mem_region[i].reg_addr = resp.mem_region_info[i].region_addr; penv->icnss_mem_region[i].size = resp.mem_region_info[i].size; penv->icnss_mem_region[i].secure_flag = resp.mem_region_info[i].secure_flag; pr_debug("%s : Memory Region: %d Addr:0x%x Size : %d Flag: %d\n", __func__, i, (unsigned int)penv->icnss_mem_region[i].reg_addr, penv->icnss_mem_region[i].size, penv->icnss_mem_region[i].secure_flag); } out: return ret; } static int wlfw_msa_ready_send_sync_msg(void) { int ret; struct wlfw_msa_ready_req_msg_v01 req; struct wlfw_msa_ready_resp_msg_v01 resp; struct msg_desc req_desc, resp_desc; if (!penv || !penv->wlfw_clnt) { ret = -ENODEV; goto out; } memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); req_desc.max_msg_len = WLFW_MSA_READY_REQ_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = QMI_WLFW_MSA_READY_REQ_V01; req_desc.ei_array = wlfw_msa_ready_req_msg_v01_ei; resp_desc.max_msg_len = WLFW_MSA_READY_RESP_MSG_V01_MAX_MSG_LEN; resp_desc.msg_id = QMI_WLFW_MSA_READY_RESP_V01; resp_desc.ei_array = wlfw_msa_ready_resp_msg_v01_ei; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { pr_err("%s: send req failed %d\n", __func__, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { pr_err("%s: QMI request failed %d %d\n", __func__, resp.resp.result, resp.resp.error); ret = resp.resp.result; goto out; } out: return ret; } static int wlfw_ind_register_send_sync_msg(void) { int ret; struct wlfw_ind_register_req_msg_v01 req; struct wlfw_ind_register_resp_msg_v01 resp; struct msg_desc req_desc, resp_desc; if (!penv || !penv->wlfw_clnt) { ret = -ENODEV; goto out; } memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); req.fw_ready_enable_valid = 1; req.fw_ready_enable = 1; req.msa_ready_enable_valid = 1; req.msa_ready_enable = 1; req.pin_connect_result_enable_valid = 1; req.pin_connect_result_enable = 1; req_desc.max_msg_len = WLFW_IND_REGISTER_REQ_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = QMI_WLFW_IND_REGISTER_REQ_V01; req_desc.ei_array = wlfw_ind_register_req_msg_v01_ei; resp_desc.max_msg_len = WLFW_IND_REGISTER_RESP_MSG_V01_MAX_MSG_LEN; resp_desc.msg_id = QMI_WLFW_IND_REGISTER_RESP_V01; resp_desc.ei_array = wlfw_ind_register_resp_msg_v01_ei; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { pr_err("%s: send req failed %d\n", __func__, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { pr_err("%s: QMI request failed %d %d\n", __func__, resp.resp.result, resp.resp.error); ret = resp.resp.result; goto out; } out: return ret; } static int wlfw_cap_send_sync_msg(void) { int ret; struct wlfw_cap_req_msg_v01 req; struct wlfw_cap_resp_msg_v01 resp; struct msg_desc req_desc, resp_desc; if (!penv || !penv->wlfw_clnt) { ret = -ENODEV; goto out; } memset(&resp, 0, sizeof(resp)); req_desc.max_msg_len = WLFW_CAP_REQ_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = QMI_WLFW_CAP_REQ_V01; req_desc.ei_array = wlfw_cap_req_msg_v01_ei; resp_desc.max_msg_len = WLFW_CAP_RESP_MSG_V01_MAX_MSG_LEN; resp_desc.msg_id = QMI_WLFW_CAP_RESP_V01; resp_desc.ei_array = wlfw_cap_resp_msg_v01_ei; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { pr_err("%s: send req failed %d\n", __func__, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { pr_err("%s: QMI request failed %d %d\n", __func__, resp.resp.result, resp.resp.error); ret = resp.resp.result; goto out; } /* store cap locally */ if (resp.chip_info_valid) penv->chip_info = resp.chip_info; if (resp.board_info_valid) penv->board_info = resp.board_info; else penv->board_info.board_id = 0xFF; if (resp.soc_info_valid) penv->soc_info = resp.soc_info; if (resp.fw_version_info_valid) penv->fw_version_info = resp.fw_version_info; pr_debug("%s: chip_id: 0x%0x, chip_family: 0x%0x, board_id: 0x%0x, soc_id: 0x%0x, fw_version: 0x%0x, fw_build_timestamp: %s", __func__, penv->chip_info.chip_id, penv->chip_info.chip_family, penv->board_info.board_id, penv->soc_info.soc_id, penv->fw_version_info.fw_version, penv->fw_version_info.fw_build_timestamp); out: return ret; } static int wlfw_wlan_mode_send_sync_msg(enum wlfw_driver_mode_enum_v01 mode) { int ret; struct wlfw_wlan_mode_req_msg_v01 req; struct wlfw_wlan_mode_resp_msg_v01 resp; struct msg_desc req_desc, resp_desc; if (!penv || !penv->wlfw_clnt) { ret = -ENODEV; goto out; } memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); req.mode = mode; req_desc.max_msg_len = WLFW_WLAN_MODE_REQ_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = QMI_WLFW_WLAN_MODE_REQ_V01; req_desc.ei_array = wlfw_wlan_mode_req_msg_v01_ei; resp_desc.max_msg_len = WLFW_WLAN_MODE_RESP_MSG_V01_MAX_MSG_LEN; resp_desc.msg_id = QMI_WLFW_WLAN_MODE_RESP_V01; resp_desc.ei_array = wlfw_wlan_mode_resp_msg_v01_ei; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { pr_err("%s: send req failed %d\n", __func__, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { pr_err("%s: QMI request failed %d %d\n", __func__, resp.resp.result, resp.resp.error); ret = resp.resp.result; goto out; } out: return ret; } static int wlfw_wlan_cfg_send_sync_msg(struct wlfw_wlan_cfg_req_msg_v01 *data) { int ret; struct wlfw_wlan_cfg_req_msg_v01 req; struct wlfw_wlan_cfg_resp_msg_v01 resp; struct msg_desc req_desc, resp_desc; if (!penv || !penv->wlfw_clnt) { return -ENODEV; goto out; } memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); memcpy(&req, data, sizeof(req)); req_desc.max_msg_len = WLFW_WLAN_CFG_REQ_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = QMI_WLFW_WLAN_CFG_REQ_V01; req_desc.ei_array = wlfw_wlan_cfg_req_msg_v01_ei; resp_desc.max_msg_len = WLFW_WLAN_CFG_RESP_MSG_V01_MAX_MSG_LEN; resp_desc.msg_id = QMI_WLFW_WLAN_CFG_RESP_V01; resp_desc.ei_array = wlfw_wlan_cfg_resp_msg_v01_ei; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { pr_err("%s: send req failed %d\n", __func__, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { pr_err("%s: QMI request failed %d %d\n", __func__, resp.resp.result, resp.resp.error); ret = resp.resp.result; goto out; } out: return ret; } static int wlfw_ini_send_sync_msg(bool enablefwlog) { int ret; struct wlfw_ini_req_msg_v01 req; struct wlfw_ini_resp_msg_v01 resp; struct msg_desc req_desc, resp_desc; if (!penv || !penv->wlfw_clnt) { ret = -ENODEV; goto out; } memset(&req, 0, sizeof(req)); memset(&resp, 0, sizeof(resp)); req.enablefwlog_valid = 1; req.enablefwlog = enablefwlog; req_desc.max_msg_len = WLFW_INI_REQ_MSG_V01_MAX_MSG_LEN; req_desc.msg_id = QMI_WLFW_INI_REQ_V01; req_desc.ei_array = wlfw_ini_req_msg_v01_ei; resp_desc.max_msg_len = WLFW_INI_RESP_MSG_V01_MAX_MSG_LEN; resp_desc.msg_id = QMI_WLFW_INI_RESP_V01; resp_desc.ei_array = wlfw_ini_resp_msg_v01_ei; ret = qmi_send_req_wait(penv->wlfw_clnt, &req_desc, &req, sizeof(req), &resp_desc, &resp, sizeof(resp), WLFW_TIMEOUT_MS); if (ret < 0) { pr_err("%s: send req failed %d\n", __func__, ret); goto out; } if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { pr_err("%s: QMI request failed %d %d\n", __func__, resp.resp.result, resp.resp.error); ret = resp.resp.result; goto out; } out: return ret; } static void icnss_qmi_wlfw_clnt_notify_work(struct work_struct *work) { int ret; if (!penv || !penv->wlfw_clnt) return; do { pr_debug("%s: Received Event\n", __func__); } while ((ret = qmi_recv_msg(penv->wlfw_clnt)) == 0); if (ret != -ENOMSG) pr_err("%s: Error receiving message\n", __func__); } static void icnss_qmi_wlfw_clnt_notify(struct qmi_handle *handle, enum qmi_event_type event, void *notify_priv) { if (!penv || !penv->wlfw_clnt) return; switch (event) { case QMI_RECV_MSG: schedule_work(&penv->qmi_recv_msg_work); break; default: pr_debug("%s: Received Event: %d\n", __func__, event); break; } } static void icnss_qmi_wlfw_clnt_ind(struct qmi_handle *handle, unsigned int msg_id, void *msg, unsigned int msg_len, void *ind_cb_priv) { if (!penv) return; pr_debug("%s: Received Ind 0x%x\n", __func__, msg_id); switch (msg_id) { case QMI_WLFW_FW_READY_IND_V01: icnss_qmi_event_post(ICNSS_QMI_EVENT_FW_READY_IND, NULL); break; case QMI_WLFW_MSA_READY_IND_V01: pr_debug("%s: Received MSA Ready Indication msg_id 0x%x\n", __func__, msg_id); break; case QMI_WLFW_PIN_CONNECT_RESULT_IND_V01: pr_debug("%s: Received Pin Connect Test Result msg_id 0x%x\n", __func__, msg_id); icnss_qmi_pin_connect_result_ind(msg, msg_len); break; default: pr_err("%s: Invalid msg_id 0x%x\n", __func__, msg_id); break; } } static int icnss_qmi_event_server_arrive(void *data) { int ret = 0; if (!penv) return -ENODEV; penv->wlfw_clnt = qmi_handle_create(icnss_qmi_wlfw_clnt_notify, penv); if (!penv->wlfw_clnt) { pr_err("%s: QMI client handle alloc failed\n", __func__); ret = -ENOMEM; goto out; } ret = qmi_connect_to_service(penv->wlfw_clnt, WLFW_SERVICE_ID_V01, WLFW_SERVICE_VERS_V01, WLFW_SERVICE_INS_ID_V01); if (ret < 0) { pr_err("%s: Server not found : %d\n", __func__, ret); goto fail; } ret = qmi_register_ind_cb(penv->wlfw_clnt, icnss_qmi_wlfw_clnt_ind, penv); if (ret < 0) { pr_err("%s: Failed to register indication callback: %d\n", __func__, ret); goto fail; } penv->state |= ICNSS_WLFW_QMI_CONNECTED; pr_info("%s: QMI Server Connected\n", __func__); ret = icnss_adrastea_power_on(); if (ret < 0) { pr_err("%s: Failed to power on hardware: %d\n", __func__, ret); goto fail; } ret = wlfw_ind_register_send_sync_msg(); if (ret < 0) { pr_err("%s: Failed to send indication message: %d\n", __func__, ret); goto err_power_on; } if (penv->msa_va) { ret = wlfw_msa_mem_info_send_sync_msg(); if (ret < 0) { pr_err("%s: Failed to send MSA info: %d\n", __func__, ret); goto err_power_on; } ret = wlfw_msa_ready_send_sync_msg(); if (ret < 0) { pr_err("%s: Failed to send MSA ready : %d\n", __func__, ret); goto err_power_on; } } else { pr_err("%s: Invalid MSA address\n", __func__); ret = -EINVAL; goto err_power_on; } ret = wlfw_cap_send_sync_msg(); if (ret < 0) { pr_err("%s: Failed to get capability: %d\n", __func__, ret); goto err_power_on; } return ret; err_power_on: ret = icnss_vreg_set(VREG_OFF); if (ret < 0) { pr_err("%s: Failed to turn off voltagre regulator: %d\n", __func__, ret); } fail: qmi_handle_destroy(penv->wlfw_clnt); penv->wlfw_clnt = NULL; out: ICNSS_ASSERT(0); return ret; } static int icnss_qmi_event_server_exit(void *data) { if (!penv || !penv->wlfw_clnt) return -ENODEV; pr_info("%s: QMI Service Disconnected\n", __func__); qmi_handle_destroy(penv->wlfw_clnt); penv->state = 0; penv->wlfw_clnt = NULL; return 0; } static int icnss_qmi_event_fw_ready_ind(void *data) { int ret = 0; if (!penv) return -ENODEV; penv->state |= ICNSS_FW_READY; if (!penv->pdev) { pr_err("%s: Device is not ready\n", __func__); ret = -ENODEV; goto out; } ret = icnss_adrastea_power_off(); if (ret < 0) { pr_err("%s: Failed to power off hardware: %d\n", __func__, ret); goto out; } if (!penv->ops || !penv->ops->probe) { pr_err("%s: WLAN driver is not registed yet\n", __func__); ret = -ENOENT; goto out; } ret = penv->ops->probe(&penv->pdev->dev); if (ret < 0) pr_err("%s: Driver probe failed: %d\n", __func__, ret); out: return ret; } static int icnss_qmi_wlfw_clnt_svc_event_notify(struct notifier_block *this, unsigned long code, void *_cmd) { int ret = 0; if (!penv) return -ENODEV; pr_debug("%s: Event Notify: code: %ld", __func__, code); switch (code) { case QMI_SERVER_ARRIVE: ret = icnss_qmi_event_post(ICNSS_QMI_EVENT_SERVER_ARRIVE, NULL); break; case QMI_SERVER_EXIT: ret = icnss_qmi_event_post(ICNSS_QMI_EVENT_SERVER_EXIT, NULL); break; default: pr_debug("%s: Invalid code: %ld", __func__, code); break; } return ret; } static void icnss_qmi_wlfw_event_work(struct work_struct *work) { struct icnss_qmi_event *event; unsigned long flags; spin_lock_irqsave(&penv->qmi_event_lock, flags); while (!list_empty(&penv->qmi_event_list)) { event = list_first_entry(&penv->qmi_event_list, struct icnss_qmi_event, list); list_del(&event->list); spin_unlock_irqrestore(&penv->qmi_event_lock, flags); switch (event->type) { case ICNSS_QMI_EVENT_SERVER_ARRIVE: icnss_qmi_event_server_arrive(event->data); break; case ICNSS_QMI_EVENT_SERVER_EXIT: icnss_qmi_event_server_exit(event->data); break; case ICNSS_QMI_EVENT_FW_READY_IND: icnss_qmi_event_fw_ready_ind(event->data); break; default: pr_debug("%s: Invalid Event type: %d", __func__, event->type); break; } kfree(event); spin_lock_irqsave(&penv->qmi_event_lock, flags); } spin_unlock_irqrestore(&penv->qmi_event_lock, flags); } static struct notifier_block wlfw_clnt_nb = { .notifier_call = icnss_qmi_wlfw_clnt_svc_event_notify, }; int icnss_register_driver(struct icnss_driver_ops *ops) { struct platform_device *pdev; int ret = 0; if (!penv || !penv->pdev) { ret = -ENODEV; goto out; } pdev = penv->pdev; if (!pdev) { ret = -ENODEV; goto out; } if (penv->ops) { pr_err("icnss: driver already registered\n"); ret = -EEXIST; goto out; } penv->ops = ops; if (penv->skip_qmi) penv->state |= ICNSS_FW_READY; /* check for all conditions before invoking probe */ if (ICNSS_IS_FW_READY(penv->state) && penv->ops->probe) { ret = icnss_vreg_set(VREG_ON); if (ret < 0) { pr_err("%s: Failed to turn on voltagre regulator: %d\n", __func__, ret); goto out; } ret = penv->ops->probe(&pdev->dev); } else { pr_err("icnss: FW is not ready\n"); ret = -ENOENT; } out: return ret; } EXPORT_SYMBOL(icnss_register_driver); int icnss_unregister_driver(struct icnss_driver_ops *ops) { int ret = 0; struct platform_device *pdev; if (!penv || !penv->pdev) { ret = -ENODEV; goto out; } pdev = penv->pdev; if (!pdev) { ret = -ENODEV; goto out; } if (!penv->ops) { pr_err("icnss: driver not registered\n"); ret = -ENOENT; goto out; } if (penv->ops->remove) penv->ops->remove(&pdev->dev); penv->ops = NULL; ret = icnss_vreg_set(VREG_OFF); if (ret < 0) pr_err("%s: Failed to turn off voltagre regulator: %d\n", __func__, ret); out: return ret; } EXPORT_SYMBOL(icnss_unregister_driver); int icnss_register_ce_irq(unsigned int ce_id, irqreturn_t (*handler)(int, void *), unsigned long flags, const char *name) { int ret = 0; unsigned int irq; struct ce_irq_list *irq_entry; if (!penv || !penv->pdev) { ret = -ENODEV; goto out; } if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { pr_err("icnss: Invalid CE ID %d\n", ce_id); ret = -EINVAL; goto out; } irq = penv->ce_irqs[ce_id]; irq_entry = &penv->ce_irq_list[ce_id]; if (irq_entry->handler || irq_entry->irq) { pr_err("icnss: handler already registered %d\n", irq); ret = -EEXIST; goto out; } ret = request_irq(irq, handler, IRQF_SHARED, name, &penv->pdev->dev); if (ret) { pr_err("icnss: IRQ not registered %d\n", irq); ret = -EINVAL; goto out; } irq_entry->irq = irq; irq_entry->handler = handler; pr_debug("icnss: IRQ registered %d\n", irq); out: return ret; } EXPORT_SYMBOL(icnss_register_ce_irq); int icnss_unregister_ce_irq(unsigned int ce_id) { int ret = 0; unsigned int irq; struct ce_irq_list *irq_entry; if (!penv || !penv->pdev) { ret = -ENODEV; goto out; } irq = penv->ce_irqs[ce_id]; irq_entry = &penv->ce_irq_list[ce_id]; if (!irq_entry->handler || !irq_entry->irq) { pr_err("icnss: handler not registered %d\n", irq); ret = -EEXIST; goto out; } free_irq(irq, &penv->pdev->dev); irq_entry->irq = 0; irq_entry->handler = NULL; out: return ret; } EXPORT_SYMBOL(icnss_unregister_ce_irq); int icnss_ce_request_irq(unsigned int ce_id, irqreturn_t (*handler)(int, void *), unsigned long flags, const char *name, void *ctx) { int ret = 0; unsigned int irq; struct ce_irq_list *irq_entry; if (!penv || !penv->pdev) { ret = -ENODEV; goto out; } if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) { pr_err("icnss: Invalid CE ID %d\n", ce_id); ret = -EINVAL; goto out; } irq = penv->ce_irqs[ce_id]; irq_entry = &penv->ce_irq_list[ce_id]; if (irq_entry->handler || irq_entry->irq) { pr_err("icnss: handler already registered %d\n", irq); ret = -EEXIST; goto out; } ret = request_irq(irq, handler, flags, name, ctx); if (ret) { pr_err("icnss: IRQ not registered %d\n", irq); ret = -EINVAL; goto out; } irq_entry->irq = irq; irq_entry->handler = handler; pr_debug("icnss: IRQ registered %d\n", irq); out: return ret; } EXPORT_SYMBOL(icnss_ce_request_irq); int icnss_ce_free_irq(unsigned int ce_id, void *ctx) { int ret = 0; unsigned int irq; struct ce_irq_list *irq_entry; if (!penv || !penv->pdev) { ret = -ENODEV; goto out; } irq = penv->ce_irqs[ce_id]; irq_entry = &penv->ce_irq_list[ce_id]; if (!irq_entry->handler || !irq_entry->irq) { pr_err("icnss: handler not registered %d\n", irq); ret = -EEXIST; goto out; } free_irq(irq, ctx); irq_entry->irq = 0; irq_entry->handler = NULL; out: return ret; } EXPORT_SYMBOL(icnss_ce_free_irq); void icnss_enable_irq(unsigned int ce_id) { unsigned int irq; if (!penv || !penv->pdev) { pr_err("icnss: platform driver not initialized\n"); return; } irq = penv->ce_irqs[ce_id]; enable_irq(irq); } EXPORT_SYMBOL(icnss_enable_irq); void icnss_disable_irq(unsigned int ce_id) { unsigned int irq; if (!penv || !penv->pdev) { pr_err("icnss: platform driver not initialized\n"); return; } irq = penv->ce_irqs[ce_id]; disable_irq(irq); } EXPORT_SYMBOL(icnss_disable_irq); int icnss_get_soc_info(struct icnss_soc_info *info) { if (!penv) { pr_err("icnss: platform driver not initialized\n"); return -EINVAL; } info->v_addr = penv->mem_base_va; info->p_addr = penv->mem_base_pa; return 0; } EXPORT_SYMBOL(icnss_get_soc_info); int icnss_set_fw_debug_mode(bool enablefwlog) { int ret; ret = wlfw_ini_send_sync_msg(enablefwlog); if (ret) pr_err("icnss: Fail to send ini, ret = %d\n", ret); return ret; } EXPORT_SYMBOL(icnss_set_fw_debug_mode); int icnss_wlan_enable(struct icnss_wlan_enable_cfg *config, enum icnss_driver_mode mode, const char *host_version) { struct wlfw_wlan_cfg_req_msg_v01 req; u32 i; int ret; memset(&req, 0, sizeof(req)); if (mode == ICNSS_WALTEST || mode == ICNSS_CCPM) goto skip; else if (!config || !host_version) { pr_err("%s: Invalid cfg pointer\n", __func__); ret = -EINVAL; goto out; } req.host_version_valid = 1; strlcpy(req.host_version, host_version, QMI_WLFW_MAX_STR_LEN_V01 + 1); req.tgt_cfg_valid = 1; if (config->num_ce_tgt_cfg > QMI_WLFW_MAX_NUM_CE_V01) req.tgt_cfg_len = QMI_WLFW_MAX_NUM_CE_V01; else req.tgt_cfg_len = config->num_ce_tgt_cfg; for (i = 0; i < req.tgt_cfg_len; i++) { req.tgt_cfg[i].pipe_num = config->ce_tgt_cfg[i].pipe_num; req.tgt_cfg[i].pipe_dir = config->ce_tgt_cfg[i].pipe_dir; req.tgt_cfg[i].nentries = config->ce_tgt_cfg[i].nentries; req.tgt_cfg[i].nbytes_max = config->ce_tgt_cfg[i].nbytes_max; req.tgt_cfg[i].flags = config->ce_tgt_cfg[i].flags; } req.svc_cfg_valid = 1; if (config->num_ce_svc_pipe_cfg > QMI_WLFW_MAX_NUM_SVC_V01) req.svc_cfg_len = QMI_WLFW_MAX_NUM_SVC_V01; else req.svc_cfg_len = config->num_ce_svc_pipe_cfg; for (i = 0; i < req.svc_cfg_len; i++) { req.svc_cfg[i].service_id = config->ce_svc_cfg[i].service_id; req.svc_cfg[i].pipe_dir = config->ce_svc_cfg[i].pipe_dir; req.svc_cfg[i].pipe_num = config->ce_svc_cfg[i].pipe_num; } req.shadow_reg_valid = 1; if (config->num_shadow_reg_cfg > QMI_WLFW_MAX_NUM_SHADOW_REG_V01) req.shadow_reg_len = QMI_WLFW_MAX_NUM_SHADOW_REG_V01; else req.shadow_reg_len = config->num_shadow_reg_cfg; memcpy(req.shadow_reg, config->shadow_reg_cfg, sizeof(struct wlfw_shadow_reg_cfg_s_v01) * req.shadow_reg_len); ret = wlfw_wlan_cfg_send_sync_msg(&req); if (ret) { pr_err("%s: Failed to send cfg, ret = %d\n", __func__, ret); goto out; } skip: ret = wlfw_wlan_mode_send_sync_msg(mode); if (ret) pr_err("%s: Failed to send mode, ret = %d\n", __func__, ret); out: if (penv->skip_qmi) ret = 0; return ret; } EXPORT_SYMBOL(icnss_wlan_enable); int icnss_wlan_disable(enum icnss_driver_mode mode) { return wlfw_wlan_mode_send_sync_msg(QMI_WLFW_OFF_V01); } EXPORT_SYMBOL(icnss_wlan_disable); int icnss_get_ce_id(int irq) { int i; for (i = 0; i < ICNSS_MAX_IRQ_REGISTRATIONS; i++) { if (penv->ce_irqs[i] == irq) return i; } pr_err("icnss: No matching CE id for irq %d\n", irq); return -EINVAL; } EXPORT_SYMBOL(icnss_get_ce_id); static ssize_t icnss_wlan_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; int ret; if (!penv) return -ENODEV; ret = kstrtoint(buf, 0, &val); if (ret) return ret; if (val == ICNSS_WALTEST || val == ICNSS_CCPM) { pr_debug("%s: WLAN Test Mode -> %d\n", __func__, val); ret = icnss_wlan_enable(NULL, val, NULL); if (ret) pr_err("%s: WLAN Test Mode %d failed with %d\n", __func__, val, ret); } else { pr_err("%s: Mode %d is not supported from command line\n", __func__, val); ret = -EINVAL; } return ret; } static DEVICE_ATTR(icnss_wlan_mode, S_IWUSR, NULL, icnss_wlan_mode_store); static int icnss_dt_parse_vreg_info(struct device *dev, struct icnss_vreg_info *vreg_info, const char *vreg_name) { int ret = 0; u32 voltage_levels[MAX_VOLTAGE_LEVEL]; char prop_name[MAX_PROP_SIZE]; struct device_node *np = dev->of_node; snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name); if (!of_parse_phandle(np, prop_name, 0)) { pr_err("%s: No vreg data found for %s\n", __func__, vreg_name); ret = -EINVAL; return ret; } vreg_info->name = vreg_name; snprintf(prop_name, MAX_PROP_SIZE, "qcom,%s-voltage-level", vreg_name); ret = of_property_read_u32_array(np, prop_name, voltage_levels, ARRAY_SIZE(voltage_levels)); if (ret) { pr_err("%s: error reading %s property\n", __func__, prop_name); return ret; } vreg_info->nominal_min = voltage_levels[0]; vreg_info->max_voltage = voltage_levels[1]; return ret; } static int icnss_get_resources(struct device *dev) { int ret = 0; struct icnss_vreg_info *vreg_info; vreg_info = &penv->vreg_info; if (vreg_info->reg) { pr_err("%s: %s regulator is already initialized\n", __func__, vreg_info->name); return ret; } vreg_info->reg = devm_regulator_get(dev, vreg_info->name); if (IS_ERR(vreg_info->reg)) { ret = PTR_ERR(vreg_info->reg); if (ret == -EPROBE_DEFER) { pr_err("%s: %s probe deferred!\n", __func__, vreg_info->name); } else { pr_err("%s: Get %s failed!\n", __func__, vreg_info->name); } } return ret; } static int icnss_release_resources(void) { int ret = 0; struct icnss_vreg_info *vreg_info = &penv->vreg_info; if (!vreg_info->reg) { pr_err("%s: regulator is not initialized\n", __func__); return -ENOENT; } devm_regulator_put(vreg_info->reg); return ret; } static int icnss_probe(struct platform_device *pdev) { int ret = 0; struct resource *res; int i; struct device *dev = &pdev->dev; if (penv) { pr_err("%s: penv is already initialized\n", __func__); return -EEXIST; } penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL); if (!penv) return -ENOMEM; penv->pdev = pdev; ret = icnss_dt_parse_vreg_info(dev, &penv->vreg_info, "vdd-io"); if (ret < 0) { pr_err("%s: failed parsing vdd io data\n", __func__); goto out; } ret = icnss_get_resources(dev); if (ret < 0) { pr_err("%s: Regulator setup failed (%d)\n", __func__, ret); goto out; } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "membase"); if (!res) { pr_err("%s: Memory base not found\n", __func__); ret = -EINVAL; goto release_regulator; } penv->mem_base_pa = res->start; penv->mem_base_va = ioremap(penv->mem_base_pa, resource_size(res)); if (!penv->mem_base_va) { pr_err("%s: ioremap failed\n", __func__); ret = -EINVAL; goto release_regulator; } for (i = 0; i < ICNSS_MAX_IRQ_REGISTRATIONS; i++) { res = platform_get_resource(pdev, IORESOURCE_IRQ, i); if (!res) { pr_err("%s: Fail to get IRQ-%d\n", __func__, i); ret = -ENODEV; goto release_regulator; } else { penv->ce_irqs[i] = res->start; } } if (of_property_read_u32(dev->of_node, "qcom,wlan-msa-memory", &penv->msa_mem_size) == 0) { if (penv->msa_mem_size) { penv->msa_va = dma_alloc_coherent(&pdev->dev, penv->msa_mem_size, &penv->msa_pa, GFP_KERNEL); if (!penv->msa_va) { pr_err("%s: DMA alloc failed\n", __func__); ret = -EINVAL; goto release_regulator; } pr_debug("%s: MAS va: %p, MSA pa: %pa\n", __func__, penv->msa_va, &penv->msa_pa); } } else { pr_err("%s: Fail to get MSA Memory Size\n", __func__); ret = -ENODEV; goto release_regulator; } penv->skip_qmi = of_property_read_bool(dev->of_node, "qcom,skip-qmi"); ret = device_create_file(dev, &dev_attr_icnss_wlan_mode); if (ret) { pr_err("%s: wlan_mode sys file creation failed\n", __func__); goto err_wlan_mode; } spin_lock_init(&penv->qmi_event_lock); penv->qmi_event_wq = alloc_workqueue("icnss_qmi_event", 0, 0); if (!penv->qmi_event_wq) { pr_err("%s: workqueue creation failed\n", __func__); ret = -EFAULT; goto err_workqueue; } INIT_WORK(&penv->qmi_event_work, icnss_qmi_wlfw_event_work); INIT_WORK(&penv->qmi_recv_msg_work, icnss_qmi_wlfw_clnt_notify_work); INIT_LIST_HEAD(&penv->qmi_event_list); ret = qmi_svc_event_notifier_register(WLFW_SERVICE_ID_V01, WLFW_SERVICE_VERS_V01, WLFW_SERVICE_INS_ID_V01, &wlfw_clnt_nb); if (ret < 0) { pr_err("%s: notifier register failed\n", __func__); goto err_qmi; } pr_debug("icnss: Platform driver probed successfully\n"); return ret; err_qmi: if (penv->qmi_event_wq) destroy_workqueue(penv->qmi_event_wq); err_workqueue: device_remove_file(&pdev->dev, &dev_attr_icnss_wlan_mode); err_wlan_mode: if (penv->msa_va) dma_free_coherent(&pdev->dev, penv->msa_mem_size, penv->msa_va, penv->msa_pa); release_regulator: ret = icnss_release_resources(); if (ret < 0) pr_err("%s: fail to release the platform resource\n", __func__); out: devm_kfree(&pdev->dev, penv); penv = NULL; return ret; } static int icnss_remove(struct platform_device *pdev) { int ret = 0; qmi_svc_event_notifier_unregister(WLFW_SERVICE_ID_V01, WLFW_SERVICE_VERS_V01, WLFW_SERVICE_INS_ID_V01, &wlfw_clnt_nb); if (penv->qmi_event_wq) destroy_workqueue(penv->qmi_event_wq); device_remove_file(&pdev->dev, &dev_attr_icnss_wlan_mode); if (penv->msa_va) dma_free_coherent(&pdev->dev, penv->msa_mem_size, penv->msa_va, penv->msa_pa); ret = icnss_vreg_set(VREG_OFF); if (ret < 0) pr_err("%s: Failed to turn off voltagre regulator: %d\n", __func__, ret); ret = icnss_release_resources(); if (ret < 0) pr_err("%s: fail to release the platform resource\n", __func__); return ret; } static const struct of_device_id icnss_dt_match[] = { {.compatible = "qcom,icnss"}, {} }; MODULE_DEVICE_TABLE(of, icnss_dt_match); static struct platform_driver icnss_driver = { .probe = icnss_probe, .remove = icnss_remove, .driver = { .name = "icnss", .owner = THIS_MODULE, .of_match_table = icnss_dt_match, }, }; static int __init icnss_initialize(void) { return platform_driver_register(&icnss_driver); } static void __exit icnss_exit(void) { platform_driver_unregister(&icnss_driver); } module_init(icnss_initialize); module_exit(icnss_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION(DEVICE "iCNSS CORE platform driver");