blob: 1459eef7071d157de3efffdf27a3c4c7201eabf1 [file] [log] [blame]
/*
* Copyright (c) 2020 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/elf.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/soc/qcom/mdt_loader.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/delay.h>
#include <linux/qcom_scm.h>
#include <linux/clk.h>
#include <linux/bt.h>
static bool auto_load;
module_param(auto_load, bool, 0644);
unsigned
int m0_btss_load_address(struct rproc *rproc, const struct firmware *fw)
{
int i;
struct elf32_hdr *ehdr;
struct elf32_phdr *phdr;
struct elf32_phdr *phdrs;
phys_addr_t min_addr = PHYS_ADDR_MAX;
ehdr = (struct elf32_hdr *)fw->data;
phdrs = (struct elf32_phdr *)(ehdr + 1);
for (i = 0; i < ehdr->e_phnum; i++) {
phdr = &phdrs[i];
if ((phdr->p_type != PT_LOAD) || !phdr->p_memsz)
continue;
if (phdr->p_vaddr < min_addr)
break;
}
return phdr->p_vaddr;
}
int m0_btss_start(struct rproc *rproc)
{
int ret;
struct bt_descriptor *btDesc = rproc->priv;
if (rproc->state == RPROC_OFFLINE) {
ret = bt_ipc_init(btDesc);
if (ret) {
dev_err(rproc->dev.parent, "%s err initializing IPC\n",
__func__);
return ret;
}
if (!btDesc->nosecure) {
ret = qcom_scm_pas_auth_and_reset(PAS_ID, 0, CMD_ID);
if (ret) {
dev_err(rproc->dev.parent,
"secure reset failed\n");
return ret;
}
} else {
ret = reset_control_deassert(btDesc->btss_reset);
if (ret) {
dev_err(rproc->dev.parent,
"non secure reset failed\n");
return ret;
}
mdelay(50);
writel(0x1, btDesc->warm_reset + BT_M0_WARM_RST);
writel(0x0, btDesc->warm_reset + BT_M0_WARM_RST_ORIDE);
}
dev_info(rproc->dev.parent, "%s\n", __func__);
}
return ret;
}
int m0_btss_stop(struct rproc *rproc)
{
int ret;
struct bt_descriptor *btDesc = rproc->priv;
if (!atomic_read(&rproc->power) && (rproc->state == RPROC_RUNNING ||
rproc->state == RPROC_CRASHED)) {
bt_ipc_deinit(btDesc);
if (!btDesc->nosecure) {
ret = qcom_scm_pas_shutdown(PAS_ID);
if (ret) {
dev_err(rproc->dev.parent, "failed, ret = %d\n",
ret);
return ret;
}
} else {
ret = reset_control_assert(btDesc->btss_reset);
if (ret) {
dev_err(rproc->dev.parent,
"non secure assert failed, ret = %d\n", ret);
return ret;
}
mdelay(50);
writel(0x0, btDesc->warm_reset + BT_M0_WARM_RST);
writel(0x1, btDesc->warm_reset + BT_M0_WARM_RST_ORIDE);
mdelay(50);
ret = reset_control_deassert(btDesc->btss_reset);
if (ret) {
dev_err(rproc->dev.parent,
"non secure deassert failed, ret = %d\n", ret);
return ret;
}
}
dev_info(rproc->dev.parent, "%s\n", __func__);
}
return ret;
}
int m0_btss_load(struct rproc *rproc, const struct firmware *fw)
{
int ret;
uint32_t offset;
struct bt_descriptor *btDesc = rproc->priv;
offset = m0_btss_load_address(rproc, fw);
if (!btDesc->nosecure) {
ret = qcom_mdt_load(rproc->dev.parent, fw, rproc->firmware,
PAS_ID, btDesc->btmem.virt + offset,
btDesc->btmem.phys, btDesc->btmem.size,
&btDesc->btmem.reloc);
} else {
ret = qti_scm_load_otp(PAS_ID);
if (ret)
dev_info(rproc->dev.parent, "secure OTP copy failed\n");
ret = qcom_mdt_load_no_init(rproc->dev.parent, fw,
rproc->firmware, 0, btDesc->btmem.virt + offset,
btDesc->btmem.phys, btDesc->btmem.size,
&btDesc->btmem.reloc);
}
if (ret)
dev_err(rproc->dev.parent,
"Could not load firmware, ret = %d\n", ret);
else
dev_info(rproc->dev.parent, "%s\n", __func__);
return ret;
}
static const struct rproc_ops m0_btss_ops = {
.start = m0_btss_start,
.stop = m0_btss_stop,
.load = m0_btss_load,
.get_boot_addr = rproc_elf_get_boot_addr,
};
static int bt_rproc_probe(struct platform_device *pdev)
{
int ret;
struct bt_descriptor *btDesc = *((struct bt_descriptor **)
pdev->dev.platform_data);
struct rproc *rproc;
ret = clk_prepare_enable(btDesc->lpo_clk);
if (ret) {
dev_err(&pdev->dev, "failed to prepare/enable clock\n");
return ret;
}
rproc = rproc_alloc(&pdev->dev, pdev->name, &m0_btss_ops,
btDesc->fw_name, 0);
if (!rproc) {
dev_err(&pdev->dev, "failed to allocate rproc\n");
return -ENOMEM;
}
rproc->priv = btDesc;
rproc->auto_boot = auto_load;
if (!rproc->dev.class->p) {
dev_err(&pdev->dev, "class not registered defering probe\n");
return -EPROBE_DEFER;
}
ret = reset_control_deassert(btDesc->btss_reset);
if (ret) {
dev_err(&btDesc->pdev->dev, "btss_reset failed\n");
rproc_free(rproc);
return ret;
}
ret = rproc_add(rproc);
if (ret) {
dev_err(&pdev->dev, "rproc_add failed, ret = %d\n", ret);
rproc_free(rproc);
return ret;
}
if (of_property_read_bool(btDesc->pdev->dev.of_node,
"qcom,bt-running")) {
rproc->state = RPROC_RUNNING;
atomic_inc(&rproc->power);
dev_info(&btDesc->pdev->dev, "Started at bootloader\n");
}
platform_set_drvdata(pdev, rproc);
ret = qti_scm_pil_cfg_available();
if (ret) {
ret = qti_scm_pil_cfg(PAS_ID, 0x1);
if (ret) {
dev_err(rproc->dev.parent,
"Failed to update XO/TCXO");
return ret;
}
dev_info(rproc->dev.parent, "Updated XO/TCXO config\n");
} else {
dev_info(&pdev->dev, "SCM call not available\n");
}
dev_info(&pdev->dev, "Probed\n");
return 0;
}
static int bt_rproc_remove(struct platform_device *pdev)
{
struct rproc *rproc = platform_get_drvdata(pdev);
struct bt_descriptor *btDesc = rproc->priv;
atomic_set(&btDesc->state, 0);
rproc_del(rproc);
rproc_free(rproc);
clk_disable_unprepare(btDesc->lpo_clk);
return 0;
}
static struct platform_driver bt_rproc_driver = {
.probe = bt_rproc_probe,
.remove = bt_rproc_remove,
.driver = {
.name = "bt_rproc_driver",
.owner = THIS_MODULE,
},
};
static int __init bt_rproc_init(void)
{
int ret;
ret = platform_driver_register(&bt_rproc_driver);
if (ret)
pr_err("%s: plat_driver registeration failed\n", __func__);
return ret;
}
static void __exit bt_rproc_exit(void)
{
platform_driver_unregister(&bt_rproc_driver);
}
module_init(bt_rproc_init);
module_exit(bt_rproc_exit);
MODULE_DESCRIPTION("QTI Technologies, Inc.");
MODULE_LICENSE("GPL v2");