diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-16 16:05:01 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-16 16:05:01 -0800 |
commit | cf9b0772f2e410645fece13b749bd56505b998b8 (patch) | |
tree | 8b171a2c49d1e9e41d4e43fb91602e664cde8551 /drivers/firmware | |
parent | 527d1470744d338c912f94bc1f4dba08ffdff349 (diff) | |
parent | 339cd0ea082287ea8e2b7e7159a5a33665a2cbe3 (diff) |
Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC driver updates from Arnd Bergmann:
"This branch contains platform-related driver updates for ARM and
ARM64, these are the areas that bring the changes:
New drivers:
- driver support for Renesas R-Car V3M (R8A77970)
- power management support for Amlogic GX
- a new driver for the Tegra BPMP thermal sensor
- a new bus driver for Technologic Systems NBUS
Changes for subsystems that prefer to merge through arm-soc:
- the usual updates for reset controller drivers from Philipp Zabel,
with five added drivers for SoCs in the arc, meson, socfpa,
uniphier and mediatek families
- updates to the ARM SCPI and PSCI frameworks, from Sudeep Holla,
Heiner Kallweit and Lorenzo Pieralisi
Changes specific to some ARM-based SoC
- the Freescale/NXP DPAA QBMan drivers from PowerPC can now work on
ARM as well
- several changes for power management on Broadcom SoCs
- various improvements on Qualcomm, Broadcom, Amlogic, Atmel,
Mediatek
- minor Cleanups for Samsung, TI OMAP SoCs"
[ NOTE! This doesn't work without the previous ARM SoC device-tree pull,
because the R8A77970 driver is missing a header file that came from
that pull.
The fact that this got merged afterwards only fixes it at this point,
and bisection of that driver will fail if/when you walk into the
history of that driver. - Linus ]
* tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (96 commits)
soc: amlogic: meson-gx-pwrc-vpu: fix power-off when powered by bootloader
bus: add driver for the Technologic Systems NBUS
memory: omap-gpmc: Remove deprecated gpmc_update_nand_reg()
soc: qcom: remove unused label
soc: amlogic: gx pm domain: add PM and OF dependencies
drivers/firmware: psci_checker: Add missing destroy_timer_on_stack()
dt-bindings: power: add amlogic meson power domain bindings
soc: amlogic: add Meson GX VPU Domains driver
soc: qcom: Remote filesystem memory driver
dt-binding: soc: qcom: Add binding for rmtfs memory
of: reserved_mem: Accessor for acquiring reserved_mem
of/platform: Generalize /reserved-memory handling
soc: mediatek: pwrap: fix fatal compiler error
soc: mediatek: pwrap: fix compiler errors
arm64: mediatek: cleanup message for platform selection
soc: Allow test-building of MediaTek drivers
soc: mediatek: place Kconfig for all SoC drivers under menu
soc: mediatek: pwrap: add support for MT7622 SoC
soc: mediatek: pwrap: add common way for setup CS timing extenstion
soc: mediatek: pwrap: add MediaTek MT6380 as one slave of pwrap
..
Diffstat (limited to 'drivers/firmware')
-rw-r--r-- | drivers/firmware/Kconfig | 11 | ||||
-rw-r--r-- | drivers/firmware/arm_scpi.c | 216 | ||||
-rw-r--r-- | drivers/firmware/psci_checker.c | 1 | ||||
-rw-r--r-- | drivers/firmware/qcom_scm-32.c | 24 | ||||
-rw-r--r-- | drivers/firmware/qcom_scm-64.c | 44 | ||||
-rw-r--r-- | drivers/firmware/qcom_scm.c | 87 | ||||
-rw-r--r-- | drivers/firmware/qcom_scm.h | 8 | ||||
-rw-r--r-- | drivers/firmware/tegra/Makefile | 4 | ||||
-rw-r--r-- | drivers/firmware/tegra/bpmp-debugfs.c | 444 | ||||
-rw-r--r-- | drivers/firmware/tegra/bpmp.c | 31 | ||||
-rw-r--r-- | drivers/firmware/ti_sci.c | 2 |
11 files changed, 733 insertions, 139 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 6e4ed5a9c6fd..fa87a055905e 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -215,6 +215,17 @@ config QCOM_SCM_64 def_bool y depends on QCOM_SCM && ARM64 +config QCOM_SCM_DOWNLOAD_MODE_DEFAULT + bool "Qualcomm download mode enabled by default" + depends on QCOM_SCM + help + A device with "download mode" enabled will upon an unexpected + warm-restart enter a special debug mode that allows the user to + "download" memory content over USB for offline postmortem analysis. + The feature can be enabled/disabled on the kernel command line. + + Say Y here to enable "download mode" by default. + config TI_SCI_PROTOCOL tristate "TI System Control Interface (TISCI) Message Protocol" depends on TI_MESSAGE_MANAGER diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 7da9f1b83ebe..dfb373c8ba2a 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -28,6 +28,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/bitmap.h> +#include <linux/bitfield.h> #include <linux/device.h> #include <linux/err.h> #include <linux/export.h> @@ -72,21 +73,13 @@ #define MAX_DVFS_DOMAINS 8 #define MAX_DVFS_OPPS 16 -#define DVFS_LATENCY(hdr) (le32_to_cpu(hdr) >> 16) -#define DVFS_OPP_COUNT(hdr) ((le32_to_cpu(hdr) >> 8) & 0xff) - -#define PROTOCOL_REV_MINOR_BITS 16 -#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1) -#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS) -#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK) - -#define FW_REV_MAJOR_BITS 24 -#define FW_REV_MINOR_BITS 16 -#define FW_REV_PATCH_MASK ((1U << FW_REV_MINOR_BITS) - 1) -#define FW_REV_MINOR_MASK ((1U << FW_REV_MAJOR_BITS) - 1) -#define FW_REV_MAJOR(x) ((x) >> FW_REV_MAJOR_BITS) -#define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS) -#define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK) + +#define PROTO_REV_MAJOR_MASK GENMASK(31, 16) +#define PROTO_REV_MINOR_MASK GENMASK(15, 0) + +#define FW_REV_MAJOR_MASK GENMASK(31, 24) +#define FW_REV_MINOR_MASK GENMASK(23, 16) +#define FW_REV_PATCH_MASK GENMASK(15, 0) #define MAX_RX_TIMEOUT (msecs_to_jiffies(30)) @@ -311,10 +304,6 @@ struct clk_get_info { u8 name[20]; } __packed; -struct clk_get_value { - __le32 rate; -} __packed; - struct clk_set_value { __le16 id; __le16 reserved; @@ -328,7 +317,9 @@ struct legacy_clk_set_value { } __packed; struct dvfs_info { - __le32 header; + u8 domain; + u8 opp_count; + __le16 latency; struct { __le32 freq; __le32 m_volt; @@ -351,11 +342,6 @@ struct _scpi_sensor_info { char name[20]; }; -struct sensor_value { - __le32 lo_val; - __le32 hi_val; -} __packed; - struct dev_pstate_set { __le16 dev_id; u8 pstate; @@ -419,19 +405,20 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd) unsigned int len; if (scpi_info->is_legacy) { - struct legacy_scpi_shared_mem *mem = ch->rx_payload; + struct legacy_scpi_shared_mem __iomem *mem = + ch->rx_payload; /* RX Length is not replied by the legacy Firmware */ len = match->rx_len; - match->status = le32_to_cpu(mem->status); + match->status = ioread32(&mem->status); memcpy_fromio(match->rx_buf, mem->payload, len); } else { - struct scpi_shared_mem *mem = ch->rx_payload; + struct scpi_shared_mem __iomem *mem = ch->rx_payload; len = min(match->rx_len, CMD_SIZE(cmd)); - match->status = le32_to_cpu(mem->status); + match->status = ioread32(&mem->status); memcpy_fromio(match->rx_buf, mem->payload, len); } @@ -445,11 +432,11 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd) static void scpi_handle_remote_msg(struct mbox_client *c, void *msg) { struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); - struct scpi_shared_mem *mem = ch->rx_payload; + struct scpi_shared_mem __iomem *mem = ch->rx_payload; u32 cmd = 0; if (!scpi_info->is_legacy) - cmd = le32_to_cpu(mem->command); + cmd = ioread32(&mem->command); scpi_process_cmd(ch, cmd); } @@ -459,7 +446,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) unsigned long flags; struct scpi_xfer *t = msg; struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); - struct scpi_shared_mem *mem = (struct scpi_shared_mem *)ch->tx_payload; + struct scpi_shared_mem __iomem *mem = ch->tx_payload; if (t->tx_buf) { if (scpi_info->is_legacy) @@ -478,7 +465,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) } if (!scpi_info->is_legacy) - mem->command = cpu_to_le32(t->cmd); + iowrite32(t->cmd, &mem->command); } static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch) @@ -583,13 +570,13 @@ scpi_clk_get_range(u16 clk_id, unsigned long *min, unsigned long *max) static unsigned long scpi_clk_get_val(u16 clk_id) { int ret; - struct clk_get_value clk; + __le32 rate; __le16 le_clk_id = cpu_to_le16(clk_id); ret = scpi_send_message(CMD_GET_CLOCK_VALUE, &le_clk_id, - sizeof(le_clk_id), &clk, sizeof(clk)); + sizeof(le_clk_id), &rate, sizeof(rate)); - return ret ? ret : le32_to_cpu(clk.rate); + return ret ? ret : le32_to_cpu(rate); } static int scpi_clk_set_val(u16 clk_id, unsigned long rate) @@ -645,34 +632,34 @@ static int opp_cmp_func(const void *opp1, const void *opp2) static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) { + if (domain >= MAX_DVFS_DOMAINS) + return ERR_PTR(-EINVAL); + + return scpi_info->dvfs[domain] ?: ERR_PTR(-EINVAL); +} + +static int scpi_dvfs_populate_info(struct device *dev, u8 domain) +{ struct scpi_dvfs_info *info; struct scpi_opp *opp; struct dvfs_info buf; int ret, i; - if (domain >= MAX_DVFS_DOMAINS) - return ERR_PTR(-EINVAL); - - if (scpi_info->dvfs[domain]) /* data already populated */ - return scpi_info->dvfs[domain]; - ret = scpi_send_message(CMD_GET_DVFS_INFO, &domain, sizeof(domain), &buf, sizeof(buf)); if (ret) - return ERR_PTR(ret); + return ret; - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = devm_kmalloc(dev, sizeof(*info), GFP_KERNEL); if (!info) - return ERR_PTR(-ENOMEM); + return -ENOMEM; - info->count = DVFS_OPP_COUNT(buf.header); - info->latency = DVFS_LATENCY(buf.header) * 1000; /* uS to nS */ + info->count = buf.opp_count; + info->latency = le16_to_cpu(buf.latency) * 1000; /* uS to nS */ - info->opps = kcalloc(info->count, sizeof(*opp), GFP_KERNEL); - if (!info->opps) { - kfree(info); - return ERR_PTR(-ENOMEM); - } + info->opps = devm_kcalloc(dev, info->count, sizeof(*opp), GFP_KERNEL); + if (!info->opps) + return -ENOMEM; for (i = 0, opp = info->opps; i < info->count; i++, opp++) { opp->freq = le32_to_cpu(buf.opps[i].freq); @@ -682,7 +669,15 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) sort(info->opps, info->count, sizeof(*opp), opp_cmp_func, NULL); scpi_info->dvfs[domain] = info; - return info; + return 0; +} + +static void scpi_dvfs_populate(struct device *dev) +{ + int domain; + + for (domain = 0; domain < MAX_DVFS_DOMAINS; domain++) + scpi_dvfs_populate_info(dev, domain); } static int scpi_dev_domain_id(struct device *dev) @@ -713,9 +708,6 @@ static int scpi_dvfs_get_transition_latency(struct device *dev) if (IS_ERR(info)) return PTR_ERR(info); - if (!info->latency) - return 0; - return info->latency; } @@ -776,20 +768,19 @@ static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info) static int scpi_sensor_get_value(u16 sensor, u64 *val) { __le16 id = cpu_to_le16(sensor); - struct sensor_value buf; + __le64 value; int ret; ret = scpi_send_message(CMD_SENSOR_VALUE, &id, sizeof(id), - &buf, sizeof(buf)); + &value, sizeof(value)); if (ret) return ret; if (scpi_info->is_legacy) - /* only 32-bits supported, hi_val can be junk */ - *val = le32_to_cpu(buf.lo_val); + /* only 32-bits supported, upper 32 bits can be junk */ + *val = le32_to_cpup((__le32 *)&value); else - *val = (u64)le32_to_cpu(buf.hi_val) << 32 | - le32_to_cpu(buf.lo_val); + *val = le64_to_cpu(value); return 0; } @@ -862,23 +853,19 @@ static int scpi_init_versions(struct scpi_drvinfo *info) static ssize_t protocol_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); - - return sprintf(buf, "%d.%d\n", - PROTOCOL_REV_MAJOR(scpi_info->protocol_version), - PROTOCOL_REV_MINOR(scpi_info->protocol_version)); + return sprintf(buf, "%lu.%lu\n", + FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version), + FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version)); } static DEVICE_ATTR_RO(protocol_version); static ssize_t firmware_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); - - return sprintf(buf, "%d.%d.%d\n", - FW_REV_MAJOR(scpi_info->firmware_version), - FW_REV_MINOR(scpi_info->firmware_version), - FW_REV_PATCH(scpi_info->firmware_version)); + return sprintf(buf, "%lu.%lu.%lu\n", + FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version)); } static DEVICE_ATTR_RO(firmware_version); @@ -889,39 +876,13 @@ static struct attribute *versions_attrs[] = { }; ATTRIBUTE_GROUPS(versions); -static void -scpi_free_channels(struct device *dev, struct scpi_chan *pchan, int count) +static void scpi_free_channels(void *data) { + struct scpi_drvinfo *info = data; int i; - for (i = 0; i < count && pchan->chan; i++, pchan++) { - mbox_free_channel(pchan->chan); - devm_kfree(dev, pchan->xfers); - devm_iounmap(dev, pchan->rx_payload); - } -} - -static int scpi_remove(struct platform_device *pdev) -{ - int i; - struct device *dev = &pdev->dev; - struct scpi_drvinfo *info = platform_get_drvdata(pdev); - - scpi_info = NULL; /* stop exporting SCPI ops through get_scpi_ops */ - - of_platform_depopulate(dev); - sysfs_remove_groups(&dev->kobj, versions_groups); - scpi_free_channels(dev, info->channels, info->num_chans); - platform_set_drvdata(pdev, NULL); - - for (i = 0; i < MAX_DVFS_DOMAINS && info->dvfs[i]; i++) { - kfree(info->dvfs[i]->opps); - kfree(info->dvfs[i]); - } - devm_kfree(dev, info->channels); - devm_kfree(dev, info); - - return 0; + for (i = 0; i < info->num_chans; i++) + mbox_free_channel(info->channels[i].chan); } #define MAX_SCPI_XFERS 10 @@ -952,7 +913,6 @@ static int scpi_probe(struct platform_device *pdev) { int count, idx, ret; struct resource res; - struct scpi_chan *scpi_chan; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; @@ -969,13 +929,19 @@ static int scpi_probe(struct platform_device *pdev) return -ENODEV; } - scpi_chan = devm_kcalloc(dev, count, sizeof(*scpi_chan), GFP_KERNEL); - if (!scpi_chan) + scpi_info->channels = devm_kcalloc(dev, count, sizeof(struct scpi_chan), + GFP_KERNEL); + if (!scpi_info->channels) return -ENOMEM; - for (idx = 0; idx < count; idx++) { + ret = devm_add_action(dev, scpi_free_channels, scpi_info); + if (ret) + return ret; + + for (; scpi_info->num_chans < count; scpi_info->num_chans++) { resource_size_t size; - struct scpi_chan *pchan = scpi_chan + idx; + int idx = scpi_info->num_chans; + struct scpi_chan *pchan = scpi_info->channels + idx; struct mbox_client *cl = &pchan->cl; struct device_node *shmem = of_parse_phandle(np, "shmem", idx); @@ -983,15 +949,14 @@ static int scpi_probe(struct platform_device *pdev) of_node_put(shmem); if (ret) { dev_err(dev, "failed to get SCPI payload mem resource\n"); - goto err; + return ret; } size = resource_size(&res); pchan->rx_payload = devm_ioremap(dev, res.start, size); if (!pchan->rx_payload) { dev_err(dev, "failed to ioremap SCPI payload\n"); - ret = -EADDRNOTAVAIL; - goto err; + return -EADDRNOTAVAIL; } pchan->tx_payload = pchan->rx_payload + (size >> 1); @@ -1017,17 +982,11 @@ static int scpi_probe(struct platform_device *pdev) dev_err(dev, "failed to get channel%d err %d\n", idx, ret); } -err: - scpi_free_channels(dev, scpi_chan, idx); - scpi_info = NULL; return ret; } - scpi_info->channels = scpi_chan; - scpi_info->num_chans = count; scpi_info->commands = scpi_std_commands; - - platform_set_drvdata(pdev, scpi_info); + scpi_info->scpi_ops = &scpi_ops; if (scpi_info->is_legacy) { /* Replace with legacy variants */ @@ -1043,23 +1002,23 @@ err: ret = scpi_init_versions(scpi_info); if (ret) { dev_err(dev, "incorrect or no SCP firmware found\n"); - scpi_remove(pdev); return ret; } - _dev_info(dev, "SCP Protocol %d.%d Firmware %d.%d.%d version\n", - PROTOCOL_REV_MAJOR(scpi_info->protocol_version), - PROTOCOL_REV_MINOR(scpi_info->protocol_version), - FW_REV_MAJOR(scpi_info->firmware_version), - FW_REV_MINOR(scpi_info->firmware_version), - FW_REV_PATCH(scpi_info->firmware_version)); - scpi_info->scpi_ops = &scpi_ops; + scpi_dvfs_populate(dev); + + _dev_info(dev, "SCP Protocol %lu.%lu Firmware %lu.%lu.%lu version\n", + FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version), + FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version), + FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version)); - ret = sysfs_create_groups(&dev->kobj, versions_groups); + ret = devm_device_add_groups(dev, versions_groups); if (ret) dev_err(dev, "unable to create sysfs version group\n"); - return of_platform_populate(dev->of_node, NULL, NULL, dev); + return devm_of_platform_populate(dev); } static const struct of_device_id scpi_of_match[] = { @@ -1076,7 +1035,6 @@ static struct platform_driver scpi_driver = { .of_match_table = scpi_of_match, }, .probe = scpi_probe, - .remove = scpi_remove, }; module_platform_driver(scpi_driver); diff --git a/drivers/firmware/psci_checker.c b/drivers/firmware/psci_checker.c index 6523ce962865..56cf825ed779 100644 --- a/drivers/firmware/psci_checker.c +++ b/drivers/firmware/psci_checker.c @@ -340,6 +340,7 @@ static int suspend_test_thread(void *arg) * later. */ del_timer(&wakeup_timer); + destroy_timer_on_stack(&wakeup_timer); if (atomic_dec_return_relaxed(&nb_active_threads) == 0) complete(&suspend_threads_done); diff --git a/drivers/firmware/qcom_scm-32.c b/drivers/firmware/qcom_scm-32.c index 93e3b96b6dfa..68b2033bc30e 100644 --- a/drivers/firmware/qcom_scm-32.c +++ b/drivers/firmware/qcom_scm-32.c @@ -561,6 +561,12 @@ int __qcom_scm_pas_mss_reset(struct device *dev, bool reset) return ret ? : le32_to_cpu(out); } +int __qcom_scm_set_dload_mode(struct device *dev, bool enable) +{ + return qcom_scm_call_atomic2(QCOM_SCM_SVC_BOOT, QCOM_SCM_SET_DLOAD_MODE, + enable ? QCOM_SCM_SET_DLOAD_MODE : 0, 0); +} + int __qcom_scm_set_remote_state(struct device *dev, u32 state, u32 id) { struct { @@ -596,3 +602,21 @@ int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, { return -ENODEV; } + +int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, + unsigned int *val) +{ + int ret; + + ret = qcom_scm_call_atomic1(QCOM_SCM_SVC_IO, QCOM_SCM_IO_READ, addr); + if (ret >= 0) + *val = ret; + + return ret < 0 ? ret : 0; +} + +int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val) +{ + return qcom_scm_call_atomic2(QCOM_SCM_SVC_IO, QCOM_SCM_IO_WRITE, + addr, val); +} diff --git a/drivers/firmware/qcom_scm-64.c b/drivers/firmware/qcom_scm-64.c index 6e6d561708e2..3fea6f563ca9 100644 --- a/drivers/firmware/qcom_scm-64.c +++ b/drivers/firmware/qcom_scm-64.c @@ -439,3 +439,47 @@ int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, return ret; } + +int __qcom_scm_set_dload_mode(struct device *dev, bool enable) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + + desc.args[0] = QCOM_SCM_SET_DLOAD_MODE; + desc.args[1] = enable ? QCOM_SCM_SET_DLOAD_MODE : 0; + desc.arginfo = QCOM_SCM_ARGS(2); + + return qcom_scm_call(dev, QCOM_SCM_SVC_BOOT, QCOM_SCM_SET_DLOAD_MODE, + &desc, &res); +} + +int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, + unsigned int *val) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + int ret; + + desc.args[0] = addr; + desc.arginfo = QCOM_SCM_ARGS(1); + + ret = qcom_scm_call(dev, QCOM_SCM_SVC_IO, QCOM_SCM_IO_READ, + &desc, &res); + if (ret >= 0) + *val = res.a1; + + return ret < 0 ? ret : 0; +} + +int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + + desc.args[0] = addr; + desc.args[1] = val; + desc.arginfo = QCOM_SCM_ARGS(2); + + return qcom_scm_call(dev, QCOM_SCM_SVC_IO, QCOM_SCM_IO_WRITE, + &desc, &res); +} diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index bb16510d75ba..9064e559a01f 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -19,15 +19,20 @@ #include <linux/cpumask.h> #include <linux/export.h> #include <linux/dma-mapping.h> +#include <linux/module.h> #include <linux/types.h> #include <linux/qcom_scm.h> #include <linux/of.h> +#include <linux/of_address.h> #include <linux/of_platform.h> #include <linux/clk.h> #include <linux/reset-controller.h> #include "qcom_scm.h" +static bool download_mode = IS_ENABLED(CONFIG_QCOM_SCM_DOWNLOAD_MODE_DEFAULT); +module_param(download_mode, bool, 0); + #define SCM_HAS_CORE_CLK BIT(0) #define SCM_HAS_IFACE_CLK BIT(1) #define SCM_HAS_BUS_CLK BIT(2) @@ -38,6 +43,8 @@ struct qcom_scm { struct clk *iface_clk; struct clk *bus_clk; struct reset_controller_dev reset; + + u64 dload_mode_addr; }; static struct qcom_scm *__scm; @@ -333,6 +340,66 @@ int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare) } EXPORT_SYMBOL(qcom_scm_iommu_secure_ptbl_init); +int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val) +{ + return __qcom_scm_io_readl(__scm->dev, addr, val); +} +EXPORT_SYMBOL(qcom_scm_io_readl); + +int qcom_scm_io_writel(phys_addr_t addr, unsigned int val) +{ + return __qcom_scm_io_writel(__scm->dev, addr, val); +} +EXPORT_SYMBOL(qcom_scm_io_writel); + +static void qcom_scm_set_download_mode(bool enable) +{ + bool avail; + int ret = 0; + + avail = __qcom_scm_is_call_available(__scm->dev, + QCOM_SCM_SVC_BOOT, + QCOM_SCM_SET_DLOAD_MODE); + if (avail) { + ret = __qcom_scm_set_dload_mode(__scm->dev, enable); + } else if (__scm->dload_mode_addr) { + ret = __qcom_scm_io_writel(__scm->dev, __scm->dload_mode_addr, + enable ? QCOM_SCM_SET_DLOAD_MODE : 0); + } else { + dev_err(__scm->dev, + "No available mechanism for setting download mode\n"); + } + + if (ret) + dev_err(__scm->dev, "failed to set download mode: %d\n", ret); +} + +static int qcom_scm_find_dload_address(struct device *dev, u64 *addr) +{ + struct device_node *tcsr; + struct device_node *np = dev->of_node; + struct resource res; + u32 offset; + int ret; + + tcsr = of_parse_phandle(np, "qcom,dload-mode", 0); + if (!tcsr) + return 0; + + ret = of_address_to_resource(tcsr, 0, &res); + of_node_put(tcsr); + if (ret) + return ret; + + ret = of_property_read_u32_index(np, "qcom,dload-mode", 1, &offset); + if (ret < 0) + return ret; + + *addr = res.start + offset; + + return 0; +} + /** * qcom_scm_is_available() - Checks if SCM is available */ @@ -358,6 +425,10 @@ static int qcom_scm_probe(struct platform_device *pdev) if (!scm) return -ENOMEM; + ret = qcom_scm_find_dload_address(&pdev->dev, &scm->dload_mode_addr); + if (ret < 0) + return ret; + clks = (unsigned long)of_device_get_match_data(&pdev->dev); if (clks & SCM_HAS_CORE_CLK) { scm->core_clk = devm_clk_get(&pdev->dev, "core"); @@ -406,9 +477,24 @@ static int qcom_scm_probe(struct platform_device *pdev) __qcom_scm_init(); + /* + * If requested enable "download mode", from this point on warmboot + * will cause the the boot stages to enter download mode, unless + * disabled below by a clean shutdown/reboot. + */ + if (download_mode) + qcom_scm_set_download_mode(true); + return 0; } +static void qcom_scm_shutdown(struct platform_device *pdev) +{ + /* Clean shutdown, disable download mode to allow normal restart */ + if (download_mode) + qcom_scm_set_download_mode(false); +} + static const struct of_device_id qcom_scm_dt_match[] = { { .compatible = "qcom,scm-apq8064", /* FIXME: This should have .data = (void *) SCM_HAS_CORE_CLK */ @@ -436,6 +522,7 @@ static struct platform_driver qcom_scm_driver = { .of_match_table = qcom_scm_dt_match, }, .probe = qcom_scm_probe, + .shutdown = qcom_scm_shutdown, }; static int __init qcom_scm_init(void) diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h index 9bea691f30fb..83f171c23943 100644 --- a/drivers/firmware/qcom_scm.h +++ b/drivers/firmware/qcom_scm.h @@ -14,9 +14,11 @@ #define QCOM_SCM_SVC_BOOT 0x1 #define QCOM_SCM_BOOT_ADDR 0x1 +#define QCOM_SCM_SET_DLOAD_MODE 0x10 #define QCOM_SCM_BOOT_ADDR_MC 0x11 #define QCOM_SCM_SET_REMOTE_STATE 0xa extern int __qcom_scm_set_remote_state(struct device *dev, u32 state, u32 id); +extern int __qcom_scm_set_dload_mode(struct device *dev, bool enable); #define QCOM_SCM_FLAG_HLOS 0x01 #define QCOM_SCM_FLAG_COLDBOOT_MC 0x02 @@ -30,6 +32,12 @@ extern int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus); #define QCOM_SCM_CMD_CORE_HOTPLUGGED 0x10 extern void __qcom_scm_cpu_power_down(u32 flags); +#define QCOM_SCM_SVC_IO 0x5 +#define QCOM_SCM_IO_READ 0x1 +#define QCOM_SCM_IO_WRITE 0x2 +extern int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, unsigned int *val); +extern int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val); + #define QCOM_SCM_SVC_INFO 0x6 #define QCOM_IS_CALL_AVAIL_CMD 0x1 extern int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index e34a2f79e1ad..1b826dcca719 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -1,2 +1,4 @@ -obj-$(CONFIG_TEGRA_BPMP) += bpmp.o +tegra-bpmp-y = bpmp.o +tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o +obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o obj-$(CONFIG_TEGRA_IVC) += ivc.o diff --git a/drivers/firmware/tegra/bpmp-debugfs.c b/drivers/firmware/tegra/bpmp-debugfs.c new file mode 100644 index 000000000000..f7f6a0a5cb07 --- /dev/null +++ b/drivers/firmware/tegra/bpmp-debugfs.c @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/debugfs.h> +#include <linux/dma-mapping.h> +#include <linux/uaccess.h> + +#include <soc/tegra/bpmp.h> +#include <soc/tegra/bpmp-abi.h> + +struct seqbuf { + char *buf; + size_t pos; + size_t size; +}; + +static void seqbuf_init(struct seqbuf *seqbuf, void *buf, size_t size) +{ + seqbuf->buf = buf; + seqbuf->size = size; + seqbuf->pos = 0; +} + +static size_t seqbuf_avail(struct seqbuf *seqbuf) +{ + return seqbuf->pos < seqbuf->size ? seqbuf->size - seqbuf->pos : 0; +} + +static size_t seqbuf_status(struct seqbuf *seqbuf) +{ + return seqbuf->pos <= seqbuf->size ? 0 : -EOVERFLOW; +} + +static int seqbuf_eof(struct seqbuf *seqbuf) +{ + return seqbuf->pos >= seqbuf->size; +} + +static int seqbuf_read(struct seqbuf *seqbuf, void *buf, size_t nbyte) +{ + nbyte = min(nbyte, seqbuf_avail(seqbuf)); + memcpy(buf, seqbuf->buf + seqbuf->pos, nbyte); + seqbuf->pos += nbyte; + return seqbuf_status(seqbuf); +} + +static int seqbuf_read_u32(struct seqbuf *seqbuf, uint32_t *v) +{ + int err; + + err = seqbuf_read(seqbuf, v, 4); + *v = le32_to_cpu(*v); + return err; +} + +static int seqbuf_read_str(struct seqbuf *seqbuf, const char **str) +{ + *str = seqbuf->buf + seqbuf->pos; + seqbuf->pos += strnlen(*str, seqbuf_avail(seqbuf)); + seqbuf->pos++; + return seqbuf_status(seqbuf); +} + +static void seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset) +{ + seqbuf->pos += offset; +} + +/* map filename in Linux debugfs to corresponding entry in BPMP */ +static const char *get_filename(struct tegra_bpmp *bpmp, + const struct file *file, char *buf, int size) +{ + char root_path_buf[512]; + const char *root_path; + const char *filename; + size_t root_len; + + root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf, + sizeof(root_path_buf)); + if (IS_ERR(root_path)) + return NULL; + + root_len = strlen(root_path); + + filename = dentry_path(file->f_path.dentry, buf, size); + if (IS_ERR(filename)) + return NULL; + + if (strlen(filename) < root_len || + strncmp(filename, root_path, root_len)) + return NULL; + + filename += root_len; + + return filename; +} + +static int mrq_debugfs_read(struct tegra_bpmp *bpmp, + dma_addr_t name, size_t sz_name, + dma_addr_t data, size_t sz_data, + size_t *nbytes) +{ + struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_READ), + .fop = { + .fnameaddr = cpu_to_le32((uint32_t)name), + .fnamelen = cpu_to_le32((uint32_t)sz_name), + .dataaddr = cpu_to_le32((uint32_t)data), + .datalen = cpu_to_le32((uint32_t)sz_data), + }, + }; + struct mrq_debugfs_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int err; + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + + *nbytes = (size_t)resp.fop.nbytes; + + return 0; +} + +static int mrq_debugfs_write(struct tegra_bpmp *bpmp, + dma_addr_t name, size_t sz_name, + dma_addr_t data, size_t sz_data) +{ + const struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_WRITE), + .fop = { + .fnameaddr = cpu_to_le32((uint32_t)name), + .fnamelen = cpu_to_le32((uint32_t)sz_name), + .dataaddr = cpu_to_le32((uint32_t)data), + .datalen = cpu_to_le32((uint32_t)sz_data), + }, + }; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + }; + + return tegra_bpmp_transfer(bpmp, &msg); +} + +static int mrq_debugfs_dumpdir(struct tegra_bpmp *bpmp, dma_addr_t addr, + size_t size, size_t *nbytes) +{ + const struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_DUMPDIR), + .dumpdir = { + .dataaddr = cpu_to_le32((uint32_t)addr), + .datalen = cpu_to_le32((uint32_t)size), + }, + }; + struct mrq_debugfs_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int err; + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + + *nbytes = (size_t)resp.dumpdir.nbytes; + + return 0; +} + +static int debugfs_show(struct seq_file *m, void *p) +{ + struct file *file = m->private; + struct inode *inode = file_inode(file); + struct tegra_bpmp *bpmp = inode->i_private; + const size_t datasize = m->size; + const size_t namesize = SZ_256; + void *datavirt, *namevirt; + dma_addr_t dataphys, namephys; + char buf[256]; + const char *filename; + size_t len, nbytes; + int ret; + + filename = get_filename(bpmp, file, buf, sizeof(buf)); + if (!filename) + return -ENOENT; + + namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys, + GFP_KERNEL | GFP_DMA32); + if (!namevirt) + return -ENOMEM; + + datavirt = dma_alloc_coh |