Files
linux/drivers/soc/qcom/qcom_pd_mapper.c
Linus Torvalds b8979c6b4d Merge tag 'soc-drivers-6.12' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
Pull SoC driver updates from Arnd Bergmann:
 "The driver updates seem larger this time around, with changes is many
  of the SoC specific drivers, both the custom drivers/soc ones and the
  closely related subsystems (memory, bus, firmware, reset, ...).

  The at91 platform gains support for sam9x7 chips in the soc and power
  management code. This is the latest variant of one of the oldest still
  supported SoC families, using the ARM9 (ARMv5) core.

  As usual, the qualcomm snapdragon platform gets a ton of updates in
  many of their drivers to add more features and additional SoC support.
  Most of these are somewhat firmware related as the platform has a
  number of firmware based interfaces to the kernel. A notable addition
  here is the inclusion of trace events to two of these drivers.

  Herve Codina and Christophe Leroy are now sending updates for
  drivers/soc/fsl/ code through the SoC tree, this contains both PowerPC
  and Arm specific platforms and has previously been problematic to
  maintain. The first update here contains support for newer PowerPC
  variants and some cleanups.

  The turris mox firmware driver has a number of updates, mostly
  cleanups.

  The Arm SCMI firmware driver gets a major rework to modularize the
  existing code into separately loadable drivers for the various
  transports, the addition of custom NXP i.MX9 interfaces and a number
  of smaller updates.

  The Arm FF-A firmware driver gets a feature update to support the v1.2
  version of the specification.

  The reset controller drivers have some smaller cleanups and a newly
  added driver for the Intel/Mobileye EyeQ5/EyeQ6 MIPS SoCs.

  The memory controller drivers get some cleanups and refactoring for
  Tegra, TI, Freescale/NXP and a couple more platforms.

  Finally there are lots of minor updates to firmware (raspberry pi,
  tegra, imx), bus (sunxi, omap, tegra) and soc (rockchips, tegra,
  amlogic, mediatek) drivers and their DT bindings"

* tag 'soc-drivers-6.12' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (212 commits)
  firmware: imx: remove duplicate scmi_imx_misc_ctrl_get()
  platform: cznic: turris-omnia-mcu: Fix error check in omnia_mcu_register_trng()
  bus: sunxi-rsb: Simplify code with dev_err_probe()
  soc: fsl: qe: ucc: Export ucc_mux_set_grant_tsa_bkpt
  soc: fsl: cpm1: qmc: Fix dependency on fsl_soc.h
  dt-bindings: arm: rockchip: Add rk3576 compatible string to pmu.yaml
  soc: fsl: qbman: Remove redundant warnings
  soc: fsl: qbman: Use iommu_paging_domain_alloc()
  MAINTAINERS: Add QE files related to the Freescale QMC controller
  soc: fsl: cpm1: qmc: Handle QUICC Engine (QE) soft-qmc firmware
  soc: fsl: cpm1: qmc: Add support for QUICC Engine (QE) implementation
  soc: fsl: qe: Add missing PUSHSCHED command
  soc: fsl: qe: Add resource-managed muram allocators
  soc: fsl: cpm1: qmc: Introduce qmc_version
  soc: fsl: cpm1: qmc: Rename SCC_GSMRL_MODE_QMC
  soc: fsl: cpm1: qmc: Handle RPACK initialization
  soc: fsl: cpm1: qmc: Rename qmc_chan_command()
  soc: fsl: cpm1: qmc: Introduce qmc_{init,exit}_xcc() and their CPM1 version
  soc: fsl: cpm1: qmc: Introduce qmc_init_resource() and its CPM1 version
  soc: fsl: cpm1: qmc: Re-order probe() operations
  ...
2024-09-17 10:48:09 +02:00

695 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm Protection Domain mapper
*
* Copyright (c) 2023 Linaro Ltd.
*/
#include <linux/auxiliary_bus.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/refcount.h>
#include <linux/slab.h>
#include <linux/soc/qcom/qmi.h>
#include "pdr_internal.h"
#define SERVREG_QMI_VERSION 0x101
#define SERVREG_QMI_INSTANCE 0
#define TMS_SERVREG_SERVICE "tms/servreg"
struct qcom_pdm_domain_data {
const char *domain;
u32 instance_id;
/* NULL-terminated array */
const char * services[];
};
struct qcom_pdm_domain {
struct list_head list;
const char *name;
u32 instance_id;
};
struct qcom_pdm_service {
struct list_head list;
struct list_head domains;
const char *name;
};
struct qcom_pdm_data {
refcount_t refcnt;
struct qmi_handle handle;
struct list_head services;
};
static DEFINE_MUTEX(qcom_pdm_mutex); /* protects __qcom_pdm_data */
static struct qcom_pdm_data *__qcom_pdm_data;
static struct qcom_pdm_service *qcom_pdm_find(struct qcom_pdm_data *data,
const char *name)
{
struct qcom_pdm_service *service;
list_for_each_entry(service, &data->services, list) {
if (!strcmp(service->name, name))
return service;
}
return NULL;
}
static int qcom_pdm_add_service_domain(struct qcom_pdm_data *data,
const char *service_name,
const char *domain_name,
u32 instance_id)
{
struct qcom_pdm_service *service;
struct qcom_pdm_domain *domain;
service = qcom_pdm_find(data, service_name);
if (service) {
list_for_each_entry(domain, &service->domains, list) {
if (!strcmp(domain->name, domain_name))
return -EBUSY;
}
} else {
service = kzalloc(sizeof(*service), GFP_KERNEL);
if (!service)
return -ENOMEM;
INIT_LIST_HEAD(&service->domains);
service->name = service_name;
list_add_tail(&service->list, &data->services);
}
domain = kzalloc(sizeof(*domain), GFP_KERNEL);
if (!domain) {
if (list_empty(&service->domains)) {
list_del(&service->list);
kfree(service);
}
return -ENOMEM;
}
domain->name = domain_name;
domain->instance_id = instance_id;
list_add_tail(&domain->list, &service->domains);
return 0;
}
static int qcom_pdm_add_domain(struct qcom_pdm_data *data,
const struct qcom_pdm_domain_data *domain)
{
int ret;
int i;
ret = qcom_pdm_add_service_domain(data,
TMS_SERVREG_SERVICE,
domain->domain,
domain->instance_id);
if (ret)
return ret;
for (i = 0; domain->services[i]; i++) {
ret = qcom_pdm_add_service_domain(data,
domain->services[i],
domain->domain,
domain->instance_id);
if (ret)
return ret;
}
return 0;
}
static void qcom_pdm_free_domains(struct qcom_pdm_data *data)
{
struct qcom_pdm_service *service, *tservice;
struct qcom_pdm_domain *domain, *tdomain;
list_for_each_entry_safe(service, tservice, &data->services, list) {
list_for_each_entry_safe(domain, tdomain, &service->domains, list) {
list_del(&domain->list);
kfree(domain);
}
list_del(&service->list);
kfree(service);
}
}
static void qcom_pdm_get_domain_list(struct qmi_handle *qmi,
struct sockaddr_qrtr *sq,
struct qmi_txn *txn,
const void *decoded)
{
struct qcom_pdm_data *data = container_of(qmi, struct qcom_pdm_data, handle);
const struct servreg_get_domain_list_req *req = decoded;
struct servreg_get_domain_list_resp *rsp;
struct qcom_pdm_service *service;
u32 offset;
int ret;
rsp = kzalloc(sizeof(*rsp), GFP_KERNEL);
if (!rsp)
return;
offset = req->domain_offset_valid ? req->domain_offset : 0;
rsp->resp.result = QMI_RESULT_SUCCESS_V01;
rsp->resp.error = QMI_ERR_NONE_V01;
rsp->db_rev_count_valid = true;
rsp->db_rev_count = 1;
rsp->total_domains_valid = true;
rsp->total_domains = 0;
mutex_lock(&qcom_pdm_mutex);
service = qcom_pdm_find(data, req->service_name);
if (service) {
struct qcom_pdm_domain *domain;
rsp->domain_list_valid = true;
rsp->domain_list_len = 0;
list_for_each_entry(domain, &service->domains, list) {
u32 i = rsp->total_domains++;
if (i >= offset && i < SERVREG_DOMAIN_LIST_LENGTH) {
u32 j = rsp->domain_list_len++;
strscpy(rsp->domain_list[j].name, domain->name,
sizeof(rsp->domain_list[i].name));
rsp->domain_list[j].instance = domain->instance_id;
pr_debug("PDM: found %s / %d\n", domain->name,
domain->instance_id);
}
}
}
pr_debug("PDM: service '%s' offset %d returning %d domains (of %d)\n", req->service_name,
req->domain_offset_valid ? req->domain_offset : -1, rsp->domain_list_len, rsp->total_domains);
ret = qmi_send_response(qmi, sq, txn, SERVREG_GET_DOMAIN_LIST_REQ,
SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN,
servreg_get_domain_list_resp_ei, rsp);
if (ret)
pr_err("Error sending servreg response: %d\n", ret);
mutex_unlock(&qcom_pdm_mutex);
kfree(rsp);
}
static void qcom_pdm_pfr(struct qmi_handle *qmi,
struct sockaddr_qrtr *sq,
struct qmi_txn *txn,
const void *decoded)
{
const struct servreg_loc_pfr_req *req = decoded;
struct servreg_loc_pfr_resp rsp = {};
int ret;
pr_warn_ratelimited("PDM: service '%s' crash: '%s'\n", req->service, req->reason);
rsp.rsp.result = QMI_RESULT_SUCCESS_V01;
rsp.rsp.error = QMI_ERR_NONE_V01;
ret = qmi_send_response(qmi, sq, txn, SERVREG_LOC_PFR_REQ,
SERVREG_LOC_PFR_RESP_MAX_LEN,
servreg_loc_pfr_resp_ei, &rsp);
if (ret)
pr_err("Error sending servreg response: %d\n", ret);
}
static const struct qmi_msg_handler qcom_pdm_msg_handlers[] = {
{
.type = QMI_REQUEST,
.msg_id = SERVREG_GET_DOMAIN_LIST_REQ,
.ei = servreg_get_domain_list_req_ei,
.decoded_size = sizeof(struct servreg_get_domain_list_req),
.fn = qcom_pdm_get_domain_list,
},
{
.type = QMI_REQUEST,
.msg_id = SERVREG_LOC_PFR_REQ,
.ei = servreg_loc_pfr_req_ei,
.decoded_size = sizeof(struct servreg_loc_pfr_req),
.fn = qcom_pdm_pfr,
},
{ },
};
static const struct qcom_pdm_domain_data adsp_audio_pd = {
.domain = "msm/adsp/audio_pd",
.instance_id = 74,
.services = {
"avs/audio",
NULL,
},
};
static const struct qcom_pdm_domain_data adsp_charger_pd = {
.domain = "msm/adsp/charger_pd",
.instance_id = 74,
.services = { NULL },
};
static const struct qcom_pdm_domain_data adsp_root_pd = {
.domain = "msm/adsp/root_pd",
.instance_id = 74,
.services = { NULL },
};
static const struct qcom_pdm_domain_data adsp_root_pd_pdr = {
.domain = "msm/adsp/root_pd",
.instance_id = 74,
.services = {
"tms/pdr_enabled",
NULL,
},
};
static const struct qcom_pdm_domain_data adsp_sensor_pd = {
.domain = "msm/adsp/sensor_pd",
.instance_id = 74,
.services = { NULL },
};
static const struct qcom_pdm_domain_data msm8996_adsp_audio_pd = {
.domain = "msm/adsp/audio_pd",
.instance_id = 4,
.services = { NULL },
};
static const struct qcom_pdm_domain_data msm8996_adsp_root_pd = {
.domain = "msm/adsp/root_pd",
.instance_id = 4,
.services = { NULL },
};
static const struct qcom_pdm_domain_data cdsp_root_pd = {
.domain = "msm/cdsp/root_pd",
.instance_id = 76,
.services = { NULL },
};
static const struct qcom_pdm_domain_data slpi_root_pd = {
.domain = "msm/slpi/root_pd",
.instance_id = 90,
.services = { NULL },
};
static const struct qcom_pdm_domain_data slpi_sensor_pd = {
.domain = "msm/slpi/sensor_pd",
.instance_id = 90,
.services = { NULL },
};
static const struct qcom_pdm_domain_data mpss_root_pd = {
.domain = "msm/modem/root_pd",
.instance_id = 180,
.services = {
NULL,
},
};
static const struct qcom_pdm_domain_data mpss_root_pd_gps = {
.domain = "msm/modem/root_pd",
.instance_id = 180,
.services = {
"gps/gps_service",
NULL,
},
};
static const struct qcom_pdm_domain_data mpss_root_pd_gps_pdr = {
.domain = "msm/modem/root_pd",
.instance_id = 180,
.services = {
"gps/gps_service",
"tms/pdr_enabled",
NULL,
},
};
static const struct qcom_pdm_domain_data msm8996_mpss_root_pd = {
.domain = "msm/modem/root_pd",
.instance_id = 100,
.services = { NULL },
};
static const struct qcom_pdm_domain_data mpss_wlan_pd = {
.domain = "msm/modem/wlan_pd",
.instance_id = 180,
.services = {
"kernel/elf_loader",
"wlan/fw",
NULL,
},
};
static const struct qcom_pdm_domain_data *msm8996_domains[] = {
&msm8996_adsp_audio_pd,
&msm8996_adsp_root_pd,
&msm8996_mpss_root_pd,
NULL,
};
static const struct qcom_pdm_domain_data *msm8998_domains[] = {
&mpss_root_pd,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *qcm2290_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_sensor_pd,
&mpss_root_pd_gps,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *qcs404_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_sensor_pd,
&cdsp_root_pd,
&mpss_root_pd,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sc7180_domains[] = {
&adsp_audio_pd,
&adsp_root_pd_pdr,
&adsp_sensor_pd,
&mpss_root_pd_gps_pdr,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sc7280_domains[] = {
&adsp_audio_pd,
&adsp_root_pd_pdr,
&adsp_charger_pd,
&adsp_sensor_pd,
&cdsp_root_pd,
&mpss_root_pd_gps_pdr,
NULL,
};
static const struct qcom_pdm_domain_data *sc8180x_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_charger_pd,
&cdsp_root_pd,
&mpss_root_pd_gps,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sc8280xp_domains[] = {
&adsp_audio_pd,
&adsp_root_pd_pdr,
&adsp_charger_pd,
&cdsp_root_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sdm660_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_sensor_pd,
&cdsp_root_pd,
&mpss_root_pd,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sdm670_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&cdsp_root_pd,
&mpss_root_pd,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sdm845_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&cdsp_root_pd,
&mpss_root_pd,
&mpss_wlan_pd,
&slpi_root_pd,
&slpi_sensor_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sm6115_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_sensor_pd,
&cdsp_root_pd,
&mpss_root_pd_gps,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sm6350_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_sensor_pd,
&cdsp_root_pd,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sm8150_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&cdsp_root_pd,
&mpss_root_pd_gps,
&mpss_wlan_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sm8250_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&cdsp_root_pd,
&slpi_root_pd,
&slpi_sensor_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sm8350_domains[] = {
&adsp_audio_pd,
&adsp_root_pd_pdr,
&adsp_charger_pd,
&cdsp_root_pd,
&mpss_root_pd_gps,
&slpi_root_pd,
&slpi_sensor_pd,
NULL,
};
static const struct qcom_pdm_domain_data *sm8550_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_charger_pd,
&adsp_sensor_pd,
&cdsp_root_pd,
&mpss_root_pd_gps,
NULL,
};
static const struct qcom_pdm_domain_data *x1e80100_domains[] = {
&adsp_audio_pd,
&adsp_root_pd,
&adsp_charger_pd,
&adsp_sensor_pd,
&cdsp_root_pd,
NULL,
};
static const struct of_device_id qcom_pdm_domains[] __maybe_unused = {
{ .compatible = "qcom,apq8016", .data = NULL, },
{ .compatible = "qcom,apq8064", .data = NULL, },
{ .compatible = "qcom,apq8074", .data = NULL, },
{ .compatible = "qcom,apq8084", .data = NULL, },
{ .compatible = "qcom,apq8096", .data = msm8996_domains, },
{ .compatible = "qcom,msm8226", .data = NULL, },
{ .compatible = "qcom,msm8909", .data = NULL, },
{ .compatible = "qcom,msm8916", .data = NULL, },
{ .compatible = "qcom,msm8939", .data = NULL, },
{ .compatible = "qcom,msm8974", .data = NULL, },
{ .compatible = "qcom,msm8996", .data = msm8996_domains, },
{ .compatible = "qcom,msm8998", .data = msm8998_domains, },
{ .compatible = "qcom,qcm2290", .data = qcm2290_domains, },
{ .compatible = "qcom,qcs404", .data = qcs404_domains, },
{ .compatible = "qcom,sc7180", .data = sc7180_domains, },
{ .compatible = "qcom,sc7280", .data = sc7280_domains, },
{ .compatible = "qcom,sc8180x", .data = sc8180x_domains, },
{ .compatible = "qcom,sc8280xp", .data = sc8280xp_domains, },
{ .compatible = "qcom,sda660", .data = sdm660_domains, },
{ .compatible = "qcom,sdm660", .data = sdm660_domains, },
{ .compatible = "qcom,sdm670", .data = sdm670_domains, },
{ .compatible = "qcom,sdm845", .data = sdm845_domains, },
{ .compatible = "qcom,sm4250", .data = sm6115_domains, },
{ .compatible = "qcom,sm6115", .data = sm6115_domains, },
{ .compatible = "qcom,sm6350", .data = sm6350_domains, },
{ .compatible = "qcom,sm7325", .data = sc7280_domains, },
{ .compatible = "qcom,sm8150", .data = sm8150_domains, },
{ .compatible = "qcom,sm8250", .data = sm8250_domains, },
{ .compatible = "qcom,sm8350", .data = sm8350_domains, },
{ .compatible = "qcom,sm8450", .data = sm8350_domains, },
{ .compatible = "qcom,sm8550", .data = sm8550_domains, },
{ .compatible = "qcom,sm8650", .data = sm8550_domains, },
{ .compatible = "qcom,x1e80100", .data = x1e80100_domains, },
{},
};
static void qcom_pdm_stop(struct qcom_pdm_data *data)
{
qcom_pdm_free_domains(data);
/* The server is removed automatically */
qmi_handle_release(&data->handle);
kfree(data);
}
static struct qcom_pdm_data *qcom_pdm_start(void)
{
const struct qcom_pdm_domain_data * const *domains;
const struct of_device_id *match;
struct qcom_pdm_data *data;
struct device_node *root;
int ret, i;
root = of_find_node_by_path("/");
if (!root)
return ERR_PTR(-ENODEV);
match = of_match_node(qcom_pdm_domains, root);
of_node_put(root);
if (!match) {
pr_notice("PDM: no support for the platform, userspace daemon might be required.\n");
return ERR_PTR(-ENODEV);
}
domains = match->data;
if (!domains) {
pr_debug("PDM: no domains\n");
return ERR_PTR(-ENODEV);
}
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&data->services);
ret = qmi_handle_init(&data->handle, SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN,
NULL, qcom_pdm_msg_handlers);
if (ret) {
kfree(data);
return ERR_PTR(ret);
}
refcount_set(&data->refcnt, 1);
for (i = 0; domains[i]; i++) {
ret = qcom_pdm_add_domain(data, domains[i]);
if (ret)
goto err_stop;
}
ret = qmi_add_server(&data->handle, SERVREG_LOCATOR_SERVICE,
SERVREG_QMI_VERSION, SERVREG_QMI_INSTANCE);
if (ret) {
pr_err("PDM: error adding server %d\n", ret);
goto err_stop;
}
return data;
err_stop:
qcom_pdm_stop(data);
return ERR_PTR(ret);
}
static int qcom_pdm_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct qcom_pdm_data *data;
int ret = 0;
mutex_lock(&qcom_pdm_mutex);
if (!__qcom_pdm_data) {
data = qcom_pdm_start();
if (IS_ERR(data))
ret = PTR_ERR(data);
else
__qcom_pdm_data = data;
} else {
refcount_inc(&__qcom_pdm_data->refcnt);
}
auxiliary_set_drvdata(auxdev, __qcom_pdm_data);
mutex_unlock(&qcom_pdm_mutex);
return ret;
}
static void qcom_pdm_remove(struct auxiliary_device *auxdev)
{
struct qcom_pdm_data *data;
data = auxiliary_get_drvdata(auxdev);
if (!data)
return;
if (refcount_dec_and_mutex_lock(&data->refcnt, &qcom_pdm_mutex)) {
__qcom_pdm_data = NULL;
qcom_pdm_stop(data);
mutex_unlock(&qcom_pdm_mutex);
}
}
static const struct auxiliary_device_id qcom_pdm_table[] = {
{ .name = "qcom_common.pd-mapper" },
{},
};
MODULE_DEVICE_TABLE(auxiliary, qcom_pdm_table);
static struct auxiliary_driver qcom_pdm_drv = {
.name = "qcom-pdm-mapper",
.id_table = qcom_pdm_table,
.probe = qcom_pdm_probe,
.remove = qcom_pdm_remove,
};
module_auxiliary_driver(qcom_pdm_drv);
MODULE_DESCRIPTION("Qualcomm Protection Domain Mapper");
MODULE_LICENSE("GPL");