Merge "msm: subsys-pil-tz: Introduce generic TZ PIL and SSR driver"
This commit is contained in:
commit
8b4c489deb
|
@ -0,0 +1,108 @@
|
|||
* Generic Subsystem Peripheral Image Loader
|
||||
|
||||
subsys-pil-tz is a generic peripheral image loader (PIL) driver. It is
|
||||
used for loading the firmware images of the subsystems into memory and
|
||||
preparing the subsystem's processor to execute code. It's also responsible
|
||||
for shutting down the processor when it's not needed.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "pil-venus" or "pil-pronto" or "pil-bcss" or
|
||||
"pil-vpu" or "qcom,pil-q6v5-lpass" or "qcom,pil-q6v55-lpass"
|
||||
- qcom,firmware-name: Base name of the firmware image.
|
||||
|
||||
Optional properties:
|
||||
- reg: Pairs of physical base addresses and region sizes of
|
||||
memory mapped registers.
|
||||
- reg-names: Names of the bases for the above registers.
|
||||
- interrupts: Subsystem to Apps watchdog bite interrupt.
|
||||
- vdd_'reg'-supply: Reference to the regulator that supplies the corresponding
|
||||
'reg' domain.
|
||||
- proxy-reg-names: Names of the regulators that need to be turned on/off
|
||||
during proxy voting/unvoting.
|
||||
- active-reg-names: Names of the regulators that need to be turned on for the
|
||||
subsystem to run. Turned off when the subsystem is shutdown.
|
||||
- vdd_'reg'-uV-uA: Voltage and current values for the 'reg' regulator.
|
||||
- proxy-clock-names: Names of the clocks that need to be turned on/off during
|
||||
proxy voting/unvoting.
|
||||
- active-clock-names: Names of the clocks that need to be turned on for the
|
||||
subsystem to run. Turned off when the subsystem is shutdown.
|
||||
- clock-names: Names of all the clocks that are accessed by the subsystem.
|
||||
- qcom,msm-bus,name: Name of the bus client for the subsystem.
|
||||
- qcom,msm-bus,num-cases: Number of use-cases.
|
||||
- qcom,msm-bus,num-paths: Number of paths.
|
||||
- qcom,msm-bus,active-only: If not set, uses the dual context by default.
|
||||
- qcom,msm-bus,vectors-KBps: Vector array of master id, slave id, arbitrated
|
||||
bandwidth and instantaneous bandwidth.
|
||||
- qcom,pas-id: pas_id of the subsystem.
|
||||
- qcom,proxy-timeout-ms: Proxy vote timeout value for the subsystem.
|
||||
- qcom,smem-id: ID of the SMEM item for the subsystem.
|
||||
- qcom,is-not-loadable: Boolean- Present if the image does not need to
|
||||
be loaded.
|
||||
- qcom,pil-self-auth: Boolean- True if authentication is required.
|
||||
- qcom,gpio-err-fatal: GPIO used by the modem to indicate error fatal to the apps.
|
||||
- qcom,gpio-err-ready: GPIO used by the modem to indicate error ready to the apps.
|
||||
- qcom,gpio-proxy-unvote: GPIO used by the modem to trigger proxy unvoting in
|
||||
the apps.
|
||||
- qcom,gpio-force-stop: GPIO used by the apps to force the modem to shutdown.
|
||||
- qcom,gpio-stop-ack: GPIO used by the modem to ack force stop or a graceful stop
|
||||
to the apps.
|
||||
- qcom,restart-group: List of subsystems that will need to restart together.
|
||||
|
||||
Example:
|
||||
qcom,venus@fdce0000 {
|
||||
compatible = "qcom,pil-venus";
|
||||
reg = <0xfdce0000 0x4000>,
|
||||
<0xfdc80000 0x400>;
|
||||
reg-names = "wrapper_base", "vbif_base";
|
||||
|
||||
vdd-supply = <&gdsc_venus>;
|
||||
proxy-reg-names = "vdd";
|
||||
clock-names = "core_clk", "iface_clk",
|
||||
"bus_clk", "mem_clk";
|
||||
proxy-clock-names = "core_clk", "iface_clk",
|
||||
"bus_clk", "mem_clk";
|
||||
|
||||
qcom,msm-bus,name = "pil-venus";
|
||||
qcom,msm-bus,num-cases = <2>;
|
||||
qcom,msm-bus,num-paths = <1>;
|
||||
qcom,msm-bus,active-only = <0>;
|
||||
qcom,msm-bus,vectors-KBps =
|
||||
<63 512 0 0>,
|
||||
<63 512 0 304000>;
|
||||
|
||||
qcom,pas-id = <9>;
|
||||
qcom,proxy-timeout-ms = <2000>;
|
||||
qcom,firmware-name = "venus";
|
||||
};
|
||||
|
||||
qcom,lpass@fe200000 {
|
||||
compatible = "qcom,pil-q6v5-lpass";
|
||||
reg = <0xfe200000 0x00100>,
|
||||
<0xfd485100 0x00010>,
|
||||
<0xfc4016c0 0x00004>;
|
||||
reg-names = "qdsp6_base", "halt_base", "restart_reg";
|
||||
vdd_cx-supply = <&pm8841_s2_corner>;
|
||||
|
||||
interrupts = <0 162 1>;
|
||||
|
||||
vdd_cx-supply = <&pm8841_s2_corner>;
|
||||
proxy-reg-names = "vdd_cx";
|
||||
vdd_cx-uV-uA = <7 100000>;
|
||||
clock-names = "bus_clk", "xo";
|
||||
active-clock-names = "bus_clk";
|
||||
proxy-clock-names = "xo";
|
||||
|
||||
qcom,smem-id = <423>;
|
||||
qcom,pas-id = <1>;
|
||||
qcom,proxy-timeout-ms = <10000>;
|
||||
qcom,firmware-name = "adsp";
|
||||
|
||||
/* GPIO inputs from lpass */
|
||||
qcom,gpio-err-fatal = <&smp2pgpio_ssr_smp2p_2_in 0 0>;
|
||||
qcom,gpio-proxy-unvote = <&smp2pgpio_ssr_smp2p_2_in 2 0>;
|
||||
qcom,gpio-err-ready = <&smp2pgpio_ssr_smp2p_2_in 1 0>;
|
||||
qcom,gpio-stop-ack = <&smp2pgpio_ssr_smp2p_2_in 3 0>;
|
||||
|
||||
/* GPIO output to lpass */
|
||||
qcom,gpio-force-stop = <&smp2pgpio_ssr_smp2p_2_out 0 0>;
|
||||
};
|
|
@ -985,6 +985,16 @@ config MSM_PIL
|
|||
|
||||
Say yes to support these devices.
|
||||
|
||||
config MSM_PIL_SSR_GENERIC
|
||||
tristate "MSM Subsystem Boot Support"
|
||||
depends on MSM_PIL && MSM_SUBSYSTEM_RESTART
|
||||
help
|
||||
Support for booting and shutting down MSM Subsystem processors.
|
||||
This driver also monitors the SMSM status bits and the watchdog
|
||||
interrupt for the subsystem and restarts it on a watchdog bite
|
||||
or a fatal error. Subsystems include LPASS, Venus, VPU, WCNSS and
|
||||
BCSS.
|
||||
|
||||
config MSM_PIL_LPASS_QDSP6V5
|
||||
tristate "LPASS QDSP6v5 (Hexagon) Boot Support"
|
||||
depends on MSM_PIL && MSM_SUBSYSTEM_RESTART
|
||||
|
|
|
@ -37,6 +37,7 @@ obj-$(CONFIG_MSM_SMP2P_TEST) += smp2p_loopback.o smp2p_test.o smp2p_gpio_test.o
|
|||
obj-$(CONFIG_MSM_SCM) += scm.o scm-boot.o
|
||||
obj-$(CONFIG_MSM_XPU_ERR_FATAL) += scm-xpu.o
|
||||
obj-$(CONFIG_MSM_PIL) += peripheral-loader.o
|
||||
obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o
|
||||
obj-$(CONFIG_MSM_PIL) += scm-pas.o
|
||||
obj-$(CONFIG_MSM_PIL_LPASS_QDSP6V5) += pil-q6v5.o pil-q6v5-lpass.o
|
||||
obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o
|
||||
|
|
|
@ -0,0 +1,745 @@
|
|||
/* Copyright (c) 2014, 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/kernel.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <mach/msm_bus_board.h>
|
||||
#include <mach/msm_bus.h>
|
||||
#include <mach/subsystem_restart.h>
|
||||
#include <mach/ramdump.h>
|
||||
|
||||
#include <soc/qcom/smem.h>
|
||||
|
||||
#include "peripheral-loader.h"
|
||||
#include "scm-pas.h"
|
||||
|
||||
#define XO_FREQ 19200000
|
||||
#define PROXY_TIMEOUT_MS 10000
|
||||
#define MAX_SSR_REASON_LEN 81U
|
||||
#define STOP_ACK_TIMEOUT_MS 1000
|
||||
|
||||
#define desc_to_data(d) container_of(d, struct pil_tz_data, desc)
|
||||
#define subsys_to_data(d) container_of(d, struct pil_tz_data, subsys_desc)
|
||||
|
||||
/**
|
||||
* struct reg_info - regulator info
|
||||
* @reg: regulator handle
|
||||
* @uV: voltage in uV
|
||||
* @uA: current in uA
|
||||
*/
|
||||
struct reg_info {
|
||||
struct regulator *reg;
|
||||
int uV;
|
||||
int uA;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pil_tz_data
|
||||
* @regs: regulators that should be always on when the subsystem is
|
||||
* brought out of reset
|
||||
* @proxy_regs: regulators that should be on during pil proxy voting
|
||||
* @clks: clocks that should be always on when the subsystem is
|
||||
* brought out of reset
|
||||
* @proxy_clks: clocks that should be on during pil proxy voting
|
||||
* @reg_count: the number of always on regulators
|
||||
* @proxy_reg_count: the number of proxy voting regulators
|
||||
* @clk_count: the number of always on clocks
|
||||
* @proxy_clk_count: the number of proxy voting clocks
|
||||
* @smem_id: the smem id used for read the subsystem crash reason
|
||||
* @ramdump_dev: ramdump device pointer
|
||||
* @pas_id: the PAS id for tz
|
||||
* @bus_client: bus client id
|
||||
* @stop_ack: state of completion of stop ack
|
||||
* @desc: PIL descriptor
|
||||
* @subsys: subsystem device pointer
|
||||
* @subsys_desc: subsystem descriptor
|
||||
*/
|
||||
struct pil_tz_data {
|
||||
struct reg_info *regs;
|
||||
struct reg_info *proxy_regs;
|
||||
struct clk **clks;
|
||||
struct clk **proxy_clks;
|
||||
int reg_count;
|
||||
int proxy_reg_count;
|
||||
int clk_count;
|
||||
int proxy_clk_count;
|
||||
int smem_id;
|
||||
void *ramdump_dev;
|
||||
u32 pas_id;
|
||||
u32 bus_client;
|
||||
struct completion stop_ack;
|
||||
struct pil_desc desc;
|
||||
struct subsys_device *subsys;
|
||||
struct subsys_desc subsys_desc;
|
||||
};
|
||||
|
||||
static int of_read_clocks(struct device *dev, struct clk ***clks_ref,
|
||||
const char *propname)
|
||||
{
|
||||
int clk_count, i, len;
|
||||
struct clk **clks;
|
||||
|
||||
if (!of_find_property(dev->of_node, propname, &len))
|
||||
return 0;
|
||||
|
||||
clk_count = of_property_count_strings(dev->of_node, propname);
|
||||
if (IS_ERR_VALUE(clk_count)) {
|
||||
dev_err(dev, "Failed to get clock names\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clks = devm_kzalloc(dev, sizeof(struct clk *) * clk_count,
|
||||
GFP_KERNEL);
|
||||
if (!clks)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < clk_count; i++) {
|
||||
const char *clock_name;
|
||||
of_property_read_string_index(dev->of_node,
|
||||
propname, i,
|
||||
&clock_name);
|
||||
|
||||
clks[i] = devm_clk_get(dev, clock_name);
|
||||
if (IS_ERR(clks[i])) {
|
||||
int rc = PTR_ERR(clks[i]);
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(dev, "Failed to get %s clock\n",
|
||||
clock_name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Make sure rate-settable clocks' rates are set */
|
||||
if (clk_get_rate(clks[i]) == 0)
|
||||
clk_set_rate(clks[i], clk_round_rate(clks[i],
|
||||
XO_FREQ));
|
||||
}
|
||||
|
||||
*clks_ref = clks;
|
||||
return clk_count;
|
||||
}
|
||||
|
||||
static int of_read_regs(struct device *dev, struct reg_info **regs_ref,
|
||||
const char *propname)
|
||||
{
|
||||
int reg_count, i, len, rc;
|
||||
struct reg_info *regs;
|
||||
|
||||
if (!of_find_property(dev->of_node, propname, &len))
|
||||
return 0;
|
||||
|
||||
reg_count = of_property_count_strings(dev->of_node, propname);
|
||||
if (IS_ERR_VALUE(reg_count)) {
|
||||
dev_err(dev, "Failed to get regulator names\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regs = devm_kzalloc(dev, sizeof(struct reg_info) * reg_count,
|
||||
GFP_KERNEL);
|
||||
if (!regs)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < reg_count; i++) {
|
||||
const char *reg_name;
|
||||
char reg_uV_uA_name[50];
|
||||
u32 vdd_uV_uA[2];
|
||||
|
||||
of_property_read_string_index(dev->of_node,
|
||||
propname, i,
|
||||
®_name);
|
||||
|
||||
regs[i].reg = devm_regulator_get(dev, reg_name);
|
||||
if (IS_ERR(regs[i].reg)) {
|
||||
int rc = PTR_ERR(regs[i].reg);
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(dev, "Failed to get %s\n regulator",
|
||||
reg_name);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the voltage and current values for the corresponding
|
||||
* regulator. The device tree property name is "regulator_name"
|
||||
* + "-uV-uA".
|
||||
*/
|
||||
rc = snprintf(reg_uV_uA_name, ARRAY_SIZE(reg_uV_uA_name),
|
||||
"%s-uV-uA", reg_name);
|
||||
if (rc < strlen(reg_name) + 6) {
|
||||
dev_err(dev, "Failed to hold reg_uV_uA_name\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!of_find_property(dev->of_node, reg_uV_uA_name, &len))
|
||||
continue;
|
||||
|
||||
len /= sizeof(vdd_uV_uA[0]);
|
||||
|
||||
/* There should be two entries: one for uV and one for uA */
|
||||
if (len != 2) {
|
||||
dev_err(dev, "Missing uV/uA value\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32_array(dev->of_node, reg_uV_uA_name,
|
||||
vdd_uV_uA, len);
|
||||
if (rc) {
|
||||
dev_err(dev, "Failed to read uV/uA values\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
regs[i].uV = vdd_uV_uA[0];
|
||||
regs[i].uA = vdd_uV_uA[1];
|
||||
}
|
||||
|
||||
*regs_ref = regs;
|
||||
return reg_count;
|
||||
}
|
||||
|
||||
static int of_read_bus_pdata(struct platform_device *pdev,
|
||||
struct pil_tz_data *d)
|
||||
{
|
||||
struct msm_bus_scale_pdata *pdata;
|
||||
pdata = msm_bus_cl_get_pdata(pdev);
|
||||
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
|
||||
d->bus_client = msm_bus_scale_register_client(pdata);
|
||||
if (!d->bus_client)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int piltz_resc_init(struct platform_device *pdev, struct pil_tz_data *d)
|
||||
{
|
||||
int len, count, rc;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
count = of_read_clocks(dev, &d->clks, "active-clock-names");
|
||||
if (count < 0) {
|
||||
dev_err(dev, "Failed to setup clocks.\n");
|
||||
return count;
|
||||
}
|
||||
d->clk_count = count;
|
||||
|
||||
count = of_read_clocks(dev, &d->proxy_clks, "proxy-clock-names");
|
||||
if (count < 0) {
|
||||
dev_err(dev, "Failed to setup proxy clocks.\n");
|
||||
return count;
|
||||
}
|
||||
d->proxy_clk_count = count;
|
||||
|
||||
count = of_read_regs(dev, &d->regs, "active-reg-names");
|
||||
if (count < 0) {
|
||||
dev_err(dev, "Failed to setup regulators.\n");
|
||||
return count;
|
||||
}
|
||||
d->reg_count = count;
|
||||
|
||||
count = of_read_regs(dev, &d->proxy_regs, "proxy-reg-names");
|
||||
if (count < 0) {
|
||||
dev_err(dev, "Failed to setup proxy regulators.\n");
|
||||
return count;
|
||||
}
|
||||
d->proxy_reg_count = count;
|
||||
|
||||
if (of_find_property(dev->of_node, "qcom,msm-bus,name", &len)) {
|
||||
rc = of_read_bus_pdata(pdev, d);
|
||||
if (rc) {
|
||||
dev_err(dev, "Failed to setup bus scaling client.\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enable_regulators(struct device *dev, struct reg_info *regs,
|
||||
int reg_count)
|
||||
{
|
||||
int i, rc = 0;
|
||||
|
||||
for (i = 0; i < reg_count; i++) {
|
||||
if (regs[i].uV > 0) {
|
||||
rc = regulator_set_voltage(regs[i].reg,
|
||||
regs[i].uV, INT_MAX);
|
||||
if (rc) {
|
||||
dev_err(dev, "Failed to request voltage.\n");
|
||||
goto err_voltage;
|
||||
}
|
||||
}
|
||||
|
||||
if (regs[i].uA > 0) {
|
||||
rc = regulator_set_optimum_mode(regs[i].reg,
|
||||
regs[i].uA);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "Failed to set regulator mode\n");
|
||||
goto err_mode;
|
||||
}
|
||||
}
|
||||
|
||||
rc = regulator_enable(regs[i].reg);
|
||||
if (rc) {
|
||||
dev_err(dev, "Regulator enable failed\n");
|
||||
goto err_enable;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_enable:
|
||||
if (regs[i].uA > 0) {
|
||||
regulator_set_voltage(regs[i].reg, 0, INT_MAX);
|
||||
regulator_set_optimum_mode(regs[i].reg, 0);
|
||||
}
|
||||
err_mode:
|
||||
if (regs[i].uV > 0)
|
||||
regulator_set_voltage(regs[i].reg, 0, INT_MAX);
|
||||
err_voltage:
|
||||
for (i--; i >= 0; i--) {
|
||||
if (regs[i].uV > 0)
|
||||
regulator_set_voltage(regs[i].reg, 0, INT_MAX);
|
||||
|
||||
if (regs[i].uA > 0)
|
||||
regulator_set_optimum_mode(regs[i].reg, 0);
|
||||
|
||||
regulator_disable(regs[i].reg);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void disable_regulators(struct reg_info *regs, int reg_count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < reg_count; i++) {
|
||||
if (regs[i].uV > 0)
|
||||
regulator_set_voltage(regs[i].reg, 0, INT_MAX);
|
||||
|
||||
if (regs[i].uA > 0)
|
||||
regulator_set_optimum_mode(regs[i].reg, 0);
|
||||
|
||||
regulator_disable(regs[i].reg);
|
||||
}
|
||||
}
|
||||
|
||||
static int prepare_enable_clocks(struct device *dev, struct clk **clks,
|
||||
int clk_count)
|
||||
{
|
||||
int rc = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < clk_count; i++) {
|
||||
rc = clk_prepare_enable(clks[i]);
|
||||
if (rc) {
|
||||
dev_err(dev, "Clock enable failed\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
for (i--; i >= 0; i--)
|
||||
clk_disable_unprepare(clks[i]);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void disable_unprepare_clocks(struct clk **clks, int clk_count)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < clk_count; i++)
|
||||
clk_disable_unprepare(clks[i]);
|
||||
}
|
||||
|
||||
static int pil_make_proxy_vote(struct pil_desc *pil)
|
||||
{
|
||||
struct pil_tz_data *d = desc_to_data(pil);
|
||||
int rc;
|
||||
|
||||
rc = enable_regulators(pil->dev, d->proxy_regs, d->proxy_reg_count);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = prepare_enable_clocks(pil->dev, d->proxy_clks,
|
||||
d->proxy_clk_count);
|
||||
if (rc)
|
||||
goto err_clks;
|
||||
|
||||
if (d->bus_client) {
|
||||
rc = msm_bus_scale_client_update_request(d->bus_client, 1);
|
||||
if (rc) {
|
||||
dev_err(pil->dev, "bandwidth request failed\n");
|
||||
goto err_bw;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_bw:
|
||||
disable_unprepare_clocks(d->proxy_clks, d->proxy_clk_count);
|
||||
err_clks:
|
||||
disable_regulators(d->proxy_regs, d->proxy_reg_count);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void pil_remove_proxy_vote(struct pil_desc *pil)
|
||||
{
|
||||
struct pil_tz_data *d = desc_to_data(pil);
|
||||
|
||||
if (d->bus_client)
|
||||
msm_bus_scale_client_update_request(d->bus_client, 0);
|
||||
|
||||
disable_unprepare_clocks(d->proxy_clks, d->proxy_clk_count);
|
||||
|
||||
disable_regulators(d->proxy_regs, d->proxy_reg_count);
|
||||
}
|
||||
|
||||
static int pil_init_image_trusted(struct pil_desc *pil,
|
||||
const u8 *metadata, size_t size)
|
||||
{
|
||||
struct pil_tz_data *d = desc_to_data(pil);
|
||||
|
||||
return pas_init_image(d->pas_id, metadata, size);
|
||||
}
|
||||
|
||||
static int pil_mem_setup_trusted(struct pil_desc *pil, phys_addr_t addr,
|
||||
size_t size)
|
||||
{
|
||||
struct pil_tz_data *d = desc_to_data(pil);
|
||||
|
||||
return pas_mem_setup(d->pas_id, addr, size);
|
||||
}
|
||||
|
||||
static int pil_auth_and_reset(struct pil_desc *pil)
|
||||
{
|
||||
struct pil_tz_data *d = desc_to_data(pil);
|
||||
int rc;
|
||||
|
||||
rc = enable_regulators(pil->dev, d->regs, d->reg_count);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = prepare_enable_clocks(pil->dev, d->clks, d->clk_count);
|
||||
if (rc)
|
||||
goto err_clks;
|
||||
|
||||
rc = pas_auth_and_reset(d->pas_id);
|
||||
if (rc)
|
||||
goto err_reset;
|
||||
|
||||
return 0;
|
||||
err_reset:
|
||||
disable_unprepare_clocks(d->clks, d->clk_count);
|
||||
err_clks:
|
||||
disable_regulators(d->regs, d->reg_count);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int pil_shutdown_trusted(struct pil_desc *pil)
|
||||
{
|
||||
struct pil_tz_data *d = desc_to_data(pil);
|
||||
int rc;
|
||||
|
||||
rc = prepare_enable_clocks(pil->dev, d->proxy_clks,
|
||||
d->proxy_clk_count);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = pas_shutdown(d->pas_id);
|
||||
|
||||
disable_unprepare_clocks(d->proxy_clks, d->proxy_clk_count);
|
||||
|
||||
disable_unprepare_clocks(d->clks, d->clk_count);
|
||||
|
||||
disable_regulators(d->regs, d->reg_count);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct pil_reset_ops pil_ops_trusted = {
|
||||
.init_image = pil_init_image_trusted,
|
||||
.mem_setup = pil_mem_setup_trusted,
|
||||
.auth_and_reset = pil_auth_and_reset,
|
||||
.shutdown = pil_shutdown_trusted,
|
||||
.proxy_vote = pil_make_proxy_vote,
|
||||
.proxy_unvote = pil_remove_proxy_vote,
|
||||
};
|
||||
|
||||
static void log_failure_reason(const struct pil_tz_data *d)
|
||||
{
|
||||
u32 size;
|
||||
char *smem_reason, reason[MAX_SSR_REASON_LEN];
|
||||
const char *name = d->subsys_desc.name;
|
||||
|
||||
if (d->smem_id == -1)
|
||||
return;
|
||||
|
||||
smem_reason = smem_get_entry_no_rlock(d->smem_id, &size, 0,
|
||||
SMEM_ANY_HOST_FLAG);
|
||||
if (!smem_reason || !size) {
|
||||
pr_err("%s SFR: (unknown, smem_get_entry_no_rlock failed).\n",
|
||||
name);
|
||||
return;
|
||||
}
|
||||
if (!smem_reason[0]) {
|
||||
pr_err("%s SFR: (unknown, empty string found).\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
strlcpy(reason, smem_reason, min(size, sizeof(reason)));
|
||||
pr_err("%s subsystem failure reason: %s.\n", name, reason);
|
||||
|
||||
smem_reason[0] = '\0';
|
||||
wmb();
|
||||
}
|
||||
|
||||
static int subsys_shutdown(const struct subsys_desc *subsys, bool force_stop)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(subsys);
|
||||
int ret;
|
||||
|
||||
if (!subsys_get_crash_status(d->subsys) && force_stop &&
|
||||
subsys->force_stop_gpio) {
|
||||
gpio_set_value(subsys->force_stop_gpio, 1);
|
||||
ret = wait_for_completion_timeout(&d->stop_ack,
|
||||
msecs_to_jiffies(STOP_ACK_TIMEOUT_MS));
|
||||
if (!ret)
|
||||
pr_warn("Timed out on stop ack from %s.\n",
|
||||
subsys->name);
|
||||
gpio_set_value(subsys->force_stop_gpio, 0);
|
||||
}
|
||||
|
||||
pil_shutdown(&d->desc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int subsys_powerup(const struct subsys_desc *subsys)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(subsys);
|
||||
int ret = 0;
|
||||
|
||||
if (subsys->stop_ack_irq)
|
||||
INIT_COMPLETION(d->stop_ack);
|
||||
ret = pil_boot(&d->desc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int subsys_ramdump(int enable, const struct subsys_desc *subsys)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(subsys);
|
||||
|
||||
if (!enable)
|
||||
return 0;
|
||||
|
||||
return pil_do_ramdump(&d->desc, d->ramdump_dev);
|
||||
}
|
||||
|
||||
static void subsys_crash_shutdown(const struct subsys_desc *subsys)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(subsys);
|
||||
|
||||
if (subsys->force_stop_gpio > 0 &&
|
||||
!subsys_get_crash_status(d->subsys)) {
|
||||
gpio_set_value(subsys->force_stop_gpio, 1);
|
||||
msleep(STOP_ACK_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_err_fatal_intr_handler (int irq, void *dev_id)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
|
||||
pr_err("Fatal error on %s!\n", d->subsys_desc.name);
|
||||
if (subsys_get_crash_status(d->subsys)) {
|
||||
pr_err("%s: Ignoring error fatal, restart in progress\n",
|
||||
d->subsys_desc.name);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
subsys_set_crash_status(d->subsys, true);
|
||||
log_failure_reason(d);
|
||||
subsystem_restart_dev(d->subsys);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_wdog_bite_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
|
||||
pr_err("Watchdog bite received from %s!\n", d->subsys_desc.name);
|
||||
if (subsys_get_crash_status(d->subsys)) {
|
||||
pr_err("%s: Ignoring wdog bite IRQ, restart in progress\n",
|
||||
d->subsys_desc.name);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
subsys_set_crash_status(d->subsys, true);
|
||||
log_failure_reason(d);
|
||||
subsystem_restart_dev(d->subsys);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t subsys_stop_ack_intr_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct pil_tz_data *d = subsys_to_data(dev_id);
|
||||
|
||||
pr_info("Received stop ack interrupt from %s\n", d->subsys_desc.name);
|
||||
complete(&d->stop_ack);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int pil_tz_driver_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pil_tz_data *d;
|
||||
u32 proxy_timeout;
|
||||
int len, rc;
|
||||
|
||||
d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, d);
|
||||
|
||||
rc = piltz_resc_init(pdev, d);
|
||||
if (rc)
|
||||
return -ENOENT;
|
||||
|
||||
rc = of_property_read_u32(pdev->dev.of_node, "qcom,pas-id", &d->pas_id);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Failed to find the pas_id.\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name",
|
||||
&d->desc.name);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Defaulting smem_id to be not present */
|
||||
d->smem_id = -1;
|
||||
|
||||
if (of_find_property(pdev->dev.of_node, "qcom,smem-id", &len)) {
|
||||
rc = of_property_read_u32(pdev->dev.of_node, "qcom,smem-id",
|
||||
&d->smem_id);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Failed to get the smem_id.\n");
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
d->desc.dev = &pdev->dev;
|
||||
d->desc.owner = THIS_MODULE;
|
||||
d->desc.ops = &pil_ops_trusted;
|
||||
|
||||
d->desc.proxy_timeout = PROXY_TIMEOUT_MS;
|
||||
|
||||
rc = of_property_read_u32(pdev->dev.of_node, "qcom,proxy-timeout-ms",
|
||||
&proxy_timeout);
|
||||
if (!rc)
|
||||
d->desc.proxy_timeout = proxy_timeout;
|
||||
|
||||
scm_pas_init(MSM_BUS_MASTER_CRYPTO_CORE0);
|
||||
|
||||
rc = pil_desc_init(&d->desc);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
d->subsys_desc.name = d->desc.name;
|
||||
d->subsys_desc.owner = THIS_MODULE;
|
||||
d->subsys_desc.dev = &pdev->dev;
|
||||
d->subsys_desc.shutdown = subsys_shutdown;
|
||||
d->subsys_desc.powerup = subsys_powerup;
|
||||
d->subsys_desc.ramdump = subsys_ramdump;
|
||||
d->subsys_desc.crash_shutdown = subsys_crash_shutdown;
|
||||
d->subsys_desc.err_fatal_handler = subsys_err_fatal_intr_handler;
|
||||
d->subsys_desc.wdog_bite_handler = subsys_wdog_bite_irq_handler;
|
||||
d->subsys_desc.stop_ack_handler = subsys_stop_ack_intr_handler;
|
||||
|
||||
d->ramdump_dev = create_ramdump_device(d->subsys_desc.name,
|
||||
&pdev->dev);
|
||||
if (!d->ramdump_dev) {
|
||||
rc = -ENOMEM;
|
||||
goto err_ramdump;
|
||||
}
|
||||
|
||||
d->subsys = subsys_register(&d->subsys_desc);
|
||||
if (IS_ERR(d->subsys)) {
|
||||
rc = PTR_ERR(d->subsys);
|
||||
goto err_subsys;
|
||||
}
|
||||
|
||||
if (d->subsys_desc.stop_ack_irq)
|
||||
init_completion(&d->stop_ack);
|
||||
|
||||
return 0;
|
||||
err_subsys:
|
||||
destroy_ramdump_device(d->ramdump_dev);
|
||||
err_ramdump:
|
||||
pil_desc_release(&d->desc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int pil_tz_driver_exit(struct platform_device *pdev)
|
||||
{
|
||||
struct pil_tz_data *d = platform_get_drvdata(pdev);
|
||||
|
||||
subsys_unregister(d->subsys);
|
||||
|
||||
destroy_ramdump_device(d->ramdump_dev);
|
||||
|
||||
pil_desc_release(&d->desc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id pil_tz_match_table[] = {
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver pil_tz_driver = {
|
||||
.probe = pil_tz_driver_probe,
|
||||
.remove = pil_tz_driver_exit,
|
||||
.driver = {
|
||||
.name = "subsys-pil-tz",
|
||||
.of_match_table = pil_tz_match_table,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init pil_tz_init(void)
|
||||
{
|
||||
return platform_driver_register(&pil_tz_driver);
|
||||
}
|
||||
module_init(pil_tz_init);
|
||||
|
||||
static void __exit pil_tz_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&pil_tz_driver);
|
||||
}
|
||||
module_exit(pil_tz_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Support for booting subsystems");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in New Issue