diff options
| author | Ingo Molnar <mingo@kernel.org> | 2018-11-03 18:42:16 -0400 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2018-11-03 18:42:16 -0400 |
| commit | 23a12ddee1ce28065b71f14ccc695b5a0c8a64ff (patch) | |
| tree | cedaa1cde5b2557116e523c31552187804704093 /drivers/firmware | |
| parent | 98f76206b33504b934209d16196477dfa519a807 (diff) | |
| parent | bcb6fb5da77c2a228adf07cc9cb1a0c2aa2001c6 (diff) | |
Merge branch 'core/urgent' into x86/urgent, to pick up objtool fix
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'drivers/firmware')
25 files changed, 1431 insertions, 48 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 6e83880046d7..7670e8dda829 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig | |||
| @@ -289,7 +289,9 @@ config HAVE_ARM_SMCCC | |||
| 289 | source "drivers/firmware/broadcom/Kconfig" | 289 | source "drivers/firmware/broadcom/Kconfig" |
| 290 | source "drivers/firmware/google/Kconfig" | 290 | source "drivers/firmware/google/Kconfig" |
| 291 | source "drivers/firmware/efi/Kconfig" | 291 | source "drivers/firmware/efi/Kconfig" |
| 292 | source "drivers/firmware/imx/Kconfig" | ||
| 292 | source "drivers/firmware/meson/Kconfig" | 293 | source "drivers/firmware/meson/Kconfig" |
| 293 | source "drivers/firmware/tegra/Kconfig" | 294 | source "drivers/firmware/tegra/Kconfig" |
| 295 | source "drivers/firmware/xilinx/Kconfig" | ||
| 294 | 296 | ||
| 295 | endmenu | 297 | endmenu |
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index e18a041cfc53..13660a951437 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile | |||
| @@ -31,4 +31,6 @@ obj-y += meson/ | |||
| 31 | obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ | 31 | obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ |
| 32 | obj-$(CONFIG_EFI) += efi/ | 32 | obj-$(CONFIG_EFI) += efi/ |
| 33 | obj-$(CONFIG_UEFI_CPER) += efi/ | 33 | obj-$(CONFIG_UEFI_CPER) += efi/ |
| 34 | obj-y += imx/ | ||
| 34 | obj-y += tegra/ | 35 | obj-y += tegra/ |
| 36 | obj-y += xilinx/ | ||
diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c index 9dff33ea6416..204390297f4b 100644 --- a/drivers/firmware/arm_scmi/base.c +++ b/drivers/firmware/arm_scmi/base.c | |||
| @@ -208,7 +208,7 @@ static int scmi_base_discover_agent_get(const struct scmi_handle *handle, | |||
| 208 | 208 | ||
| 209 | ret = scmi_do_xfer(handle, t); | 209 | ret = scmi_do_xfer(handle, t); |
| 210 | if (!ret) | 210 | if (!ret) |
| 211 | memcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE); | 211 | strlcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE); |
| 212 | 212 | ||
| 213 | scmi_xfer_put(handle, t); | 213 | scmi_xfer_put(handle, t); |
| 214 | 214 | ||
diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index e4119eb34986..30fc04e28431 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c | |||
| @@ -111,7 +111,7 @@ static int scmi_clock_attributes_get(const struct scmi_handle *handle, | |||
| 111 | 111 | ||
| 112 | ret = scmi_do_xfer(handle, t); | 112 | ret = scmi_do_xfer(handle, t); |
| 113 | if (!ret) | 113 | if (!ret) |
| 114 | memcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE); | 114 | strlcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE); |
| 115 | else | 115 | else |
| 116 | clk->name[0] = '\0'; | 116 | clk->name[0] = '\0'; |
| 117 | 117 | ||
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c index 64342944d917..3c8ae7cc35de 100644 --- a/drivers/firmware/arm_scmi/perf.c +++ b/drivers/firmware/arm_scmi/perf.c | |||
| @@ -174,7 +174,7 @@ scmi_perf_domain_attributes_get(const struct scmi_handle *handle, u32 domain, | |||
| 174 | dom_info->mult_factor = | 174 | dom_info->mult_factor = |
| 175 | (dom_info->sustained_freq_khz * 1000) / | 175 | (dom_info->sustained_freq_khz * 1000) / |
| 176 | dom_info->sustained_perf_level; | 176 | dom_info->sustained_perf_level; |
| 177 | memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE); | 177 | strlcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE); |
| 178 | } | 178 | } |
| 179 | 179 | ||
| 180 | scmi_xfer_put(handle, t); | 180 | scmi_xfer_put(handle, t); |
| @@ -427,6 +427,33 @@ static int scmi_dvfs_freq_get(const struct scmi_handle *handle, u32 domain, | |||
| 427 | return ret; | 427 | return ret; |
| 428 | } | 428 | } |
| 429 | 429 | ||
| 430 | static int scmi_dvfs_est_power_get(const struct scmi_handle *handle, u32 domain, | ||
| 431 | unsigned long *freq, unsigned long *power) | ||
| 432 | { | ||
| 433 | struct scmi_perf_info *pi = handle->perf_priv; | ||
| 434 | struct perf_dom_info *dom; | ||
| 435 | unsigned long opp_freq; | ||
| 436 | int idx, ret = -EINVAL; | ||
| 437 | struct scmi_opp *opp; | ||
| 438 | |||
| 439 | dom = pi->dom_info + domain; | ||
| 440 | if (!dom) | ||
| 441 | return -EIO; | ||
| 442 | |||
| 443 | for (opp = dom->opp, idx = 0; idx < dom->opp_count; idx++, opp++) { | ||
| 444 | opp_freq = opp->perf * dom->mult_factor; | ||
| 445 | if (opp_freq < *freq) | ||
| 446 | continue; | ||
| 447 | |||
| 448 | *freq = opp_freq; | ||
| 449 | *power = opp->power; | ||
| 450 | ret = 0; | ||
| 451 | break; | ||
| 452 | } | ||
| 453 | |||
| 454 | return ret; | ||
| 455 | } | ||
| 456 | |||
| 430 | static struct scmi_perf_ops perf_ops = { | 457 | static struct scmi_perf_ops perf_ops = { |
| 431 | .limits_set = scmi_perf_limits_set, | 458 | .limits_set = scmi_perf_limits_set, |
| 432 | .limits_get = scmi_perf_limits_get, | 459 | .limits_get = scmi_perf_limits_get, |
| @@ -437,6 +464,7 @@ static struct scmi_perf_ops perf_ops = { | |||
| 437 | .device_opps_add = scmi_dvfs_device_opps_add, | 464 | .device_opps_add = scmi_dvfs_device_opps_add, |
| 438 | .freq_set = scmi_dvfs_freq_set, | 465 | .freq_set = scmi_dvfs_freq_set, |
| 439 | .freq_get = scmi_dvfs_freq_get, | 466 | .freq_get = scmi_dvfs_freq_get, |
| 467 | .est_power_get = scmi_dvfs_est_power_get, | ||
| 440 | }; | 468 | }; |
| 441 | 469 | ||
| 442 | static int scmi_perf_protocol_init(struct scmi_handle *handle) | 470 | static int scmi_perf_protocol_init(struct scmi_handle *handle) |
diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c index cfa033b05aed..62f3401a1f01 100644 --- a/drivers/firmware/arm_scmi/power.c +++ b/drivers/firmware/arm_scmi/power.c | |||
| @@ -106,7 +106,7 @@ scmi_power_domain_attributes_get(const struct scmi_handle *handle, u32 domain, | |||
| 106 | dom_info->state_set_notify = SUPPORTS_STATE_SET_NOTIFY(flags); | 106 | dom_info->state_set_notify = SUPPORTS_STATE_SET_NOTIFY(flags); |
| 107 | dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags); | 107 | dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags); |
| 108 | dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags); | 108 | dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags); |
| 109 | memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE); | 109 | strlcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE); |
| 110 | } | 110 | } |
| 111 | 111 | ||
| 112 | scmi_xfer_put(handle, t); | 112 | scmi_xfer_put(handle, t); |
diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c index 27f2092b9882..b53d5cc9c9f6 100644 --- a/drivers/firmware/arm_scmi/sensors.c +++ b/drivers/firmware/arm_scmi/sensors.c | |||
| @@ -140,7 +140,7 @@ static int scmi_sensor_description_get(const struct scmi_handle *handle, | |||
| 140 | s = &si->sensors[desc_index + cnt]; | 140 | s = &si->sensors[desc_index + cnt]; |
| 141 | s->id = le32_to_cpu(buf->desc[cnt].id); | 141 | s->id = le32_to_cpu(buf->desc[cnt].id); |
| 142 | s->type = SENSOR_TYPE(attrh); | 142 | s->type = SENSOR_TYPE(attrh); |
| 143 | memcpy(s->name, buf->desc[cnt].name, SCMI_MAX_STR_SIZE); | 143 | strlcpy(s->name, buf->desc[cnt].name, SCMI_MAX_STR_SIZE); |
| 144 | } | 144 | } |
| 145 | 145 | ||
| 146 | desc_index += num_returned; | 146 | desc_index += num_returned; |
diff --git a/drivers/firmware/dmi_scan.c b/drivers/firmware/dmi_scan.c index f2483548cde9..099d83e4e910 100644 --- a/drivers/firmware/dmi_scan.c +++ b/drivers/firmware/dmi_scan.c | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include <linux/ctype.h> | 5 | #include <linux/ctype.h> |
| 6 | #include <linux/dmi.h> | 6 | #include <linux/dmi.h> |
| 7 | #include <linux/efi.h> | 7 | #include <linux/efi.h> |
| 8 | #include <linux/bootmem.h> | 8 | #include <linux/memblock.h> |
| 9 | #include <linux/random.h> | 9 | #include <linux/random.h> |
| 10 | #include <asm/dmi.h> | 10 | #include <asm/dmi.h> |
| 11 | #include <asm/unaligned.h> | 11 | #include <asm/unaligned.h> |
diff --git a/drivers/firmware/efi/apple-properties.c b/drivers/firmware/efi/apple-properties.c index 60a95719ecb8..ac1654f74dc7 100644 --- a/drivers/firmware/efi/apple-properties.c +++ b/drivers/firmware/efi/apple-properties.c | |||
| @@ -20,7 +20,7 @@ | |||
| 20 | 20 | ||
| 21 | #define pr_fmt(fmt) "apple-properties: " fmt | 21 | #define pr_fmt(fmt) "apple-properties: " fmt |
| 22 | 22 | ||
| 23 | #include <linux/bootmem.h> | 23 | #include <linux/memblock.h> |
| 24 | #include <linux/efi.h> | 24 | #include <linux/efi.h> |
| 25 | #include <linux/io.h> | 25 | #include <linux/io.h> |
| 26 | #include <linux/platform_data/x86/apple.h> | 26 | #include <linux/platform_data/x86/apple.h> |
| @@ -235,7 +235,7 @@ static int __init map_properties(void) | |||
| 235 | */ | 235 | */ |
| 236 | data->len = 0; | 236 | data->len = 0; |
| 237 | memunmap(data); | 237 | memunmap(data); |
| 238 | free_bootmem_late(pa_data + sizeof(*data), data_len); | 238 | memblock_free_late(pa_data + sizeof(*data), data_len); |
| 239 | 239 | ||
| 240 | return ret; | 240 | return ret; |
| 241 | } | 241 | } |
diff --git a/drivers/firmware/efi/memmap.c b/drivers/firmware/efi/memmap.c index 5fc70520e04c..fa2904fb841f 100644 --- a/drivers/firmware/efi/memmap.c +++ b/drivers/firmware/efi/memmap.c | |||
| @@ -15,7 +15,7 @@ | |||
| 15 | 15 | ||
| 16 | static phys_addr_t __init __efi_memmap_alloc_early(unsigned long size) | 16 | static phys_addr_t __init __efi_memmap_alloc_early(unsigned long size) |
| 17 | { | 17 | { |
| 18 | return memblock_alloc(size, 0); | 18 | return memblock_phys_alloc(size, SMP_CACHE_BYTES); |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | static phys_addr_t __init __efi_memmap_alloc_late(unsigned long size) | 21 | static phys_addr_t __init __efi_memmap_alloc_late(unsigned long size) |
diff --git a/drivers/firmware/imx/Kconfig b/drivers/firmware/imx/Kconfig new file mode 100644 index 000000000000..b170c2851e48 --- /dev/null +++ b/drivers/firmware/imx/Kconfig | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | config IMX_SCU | ||
| 2 | bool "IMX SCU Protocol driver" | ||
| 3 | depends on IMX_MBOX | ||
| 4 | help | ||
| 5 | The System Controller Firmware (SCFW) is a low-level system function | ||
| 6 | which runs on a dedicated Cortex-M core to provide power, clock, and | ||
| 7 | resource management. It exists on some i.MX8 processors. e.g. i.MX8QM | ||
| 8 | (QM, QP), and i.MX8QX (QXP, DX). | ||
| 9 | |||
| 10 | This driver manages the IPC interface between host CPU and the | ||
| 11 | SCU firmware running on M4. | ||
diff --git a/drivers/firmware/imx/Makefile b/drivers/firmware/imx/Makefile new file mode 100644 index 000000000000..0ac04dfda8d4 --- /dev/null +++ b/drivers/firmware/imx/Makefile | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0 | ||
| 2 | obj-$(CONFIG_IMX_SCU) += imx-scu.o misc.o | ||
diff --git a/drivers/firmware/imx/imx-scu.c b/drivers/firmware/imx/imx-scu.c new file mode 100644 index 000000000000..2bb1a19c413f --- /dev/null +++ b/drivers/firmware/imx/imx-scu.c | |||
| @@ -0,0 +1,270 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0+ | ||
| 2 | /* | ||
| 3 | * Copyright 2018 NXP | ||
| 4 | * Author: Dong Aisheng <aisheng.dong@nxp.com> | ||
| 5 | * | ||
| 6 | * Implementation of the SCU IPC functions using MUs (client side). | ||
| 7 | * | ||
| 8 | */ | ||
| 9 | |||
| 10 | #include <linux/err.h> | ||
| 11 | #include <linux/firmware/imx/types.h> | ||
| 12 | #include <linux/firmware/imx/ipc.h> | ||
| 13 | #include <linux/interrupt.h> | ||
| 14 | #include <linux/irq.h> | ||
| 15 | #include <linux/kernel.h> | ||
| 16 | #include <linux/mailbox_client.h> | ||
| 17 | #include <linux/module.h> | ||
| 18 | #include <linux/mutex.h> | ||
| 19 | #include <linux/of_platform.h> | ||
| 20 | #include <linux/platform_device.h> | ||
| 21 | |||
| 22 | #define SCU_MU_CHAN_NUM 8 | ||
| 23 | #define MAX_RX_TIMEOUT (msecs_to_jiffies(30)) | ||
| 24 | |||
| 25 | struct imx_sc_chan { | ||
| 26 | struct imx_sc_ipc *sc_ipc; | ||
| 27 | |||
| 28 | struct mbox_client cl; | ||
| 29 | struct mbox_chan *ch; | ||
| 30 | int idx; | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct imx_sc_ipc { | ||
| 34 | /* SCU uses 4 Tx and 4 Rx channels */ | ||
| 35 | struct imx_sc_chan chans[SCU_MU_CHAN_NUM]; | ||
| 36 | struct device *dev; | ||
| 37 | struct mutex lock; | ||
| 38 | struct completion done; | ||
| 39 | |||
| 40 | /* temporarily store the SCU msg */ | ||
| 41 | u32 *msg; | ||
| 42 | u8 rx_size; | ||
| 43 | u8 count; | ||
| 44 | }; | ||
| 45 | |||
| 46 | /* | ||
| 47 | * This type is used to indicate error response for most functions. | ||
| 48 | */ | ||
| 49 | enum imx_sc_error_codes { | ||
| 50 | IMX_SC_ERR_NONE = 0, /* Success */ | ||
| 51 | IMX_SC_ERR_VERSION = 1, /* Incompatible API version */ | ||
| 52 | IMX_SC_ERR_CONFIG = 2, /* Configuration error */ | ||
| 53 | IMX_SC_ERR_PARM = 3, /* Bad parameter */ | ||
| 54 | IMX_SC_ERR_NOACCESS = 4, /* Permission error (no access) */ | ||
| 55 | IMX_SC_ERR_LOCKED = 5, /* Permission error (locked) */ | ||
| 56 | IMX_SC_ERR_UNAVAILABLE = 6, /* Unavailable (out of resources) */ | ||
| 57 | IMX_SC_ERR_NOTFOUND = 7, /* Not found */ | ||
| 58 | IMX_SC_ERR_NOPOWER = 8, /* No power */ | ||
| 59 | IMX_SC_ERR_IPC = 9, /* Generic IPC error */ | ||
| 60 | IMX_SC_ERR_BUSY = 10, /* Resource is currently busy/active */ | ||
| 61 | IMX_SC_ERR_FAIL = 11, /* General I/O failure */ | ||
| 62 | IMX_SC_ERR_LAST | ||
| 63 | }; | ||
| 64 | |||
| 65 | static int imx_sc_linux_errmap[IMX_SC_ERR_LAST] = { | ||
| 66 | 0, /* IMX_SC_ERR_NONE */ | ||
| 67 | -EINVAL, /* IMX_SC_ERR_VERSION */ | ||
| 68 | -EINVAL, /* IMX_SC_ERR_CONFIG */ | ||
| 69 | -EINVAL, /* IMX_SC_ERR_PARM */ | ||
| 70 | -EACCES, /* IMX_SC_ERR_NOACCESS */ | ||
| 71 | -EACCES, /* IMX_SC_ERR_LOCKED */ | ||
| 72 | -ERANGE, /* IMX_SC_ERR_UNAVAILABLE */ | ||
| 73 | -EEXIST, /* IMX_SC_ERR_NOTFOUND */ | ||
| 74 | -EPERM, /* IMX_SC_ERR_NOPOWER */ | ||
| 75 | -EPIPE, /* IMX_SC_ERR_IPC */ | ||
| 76 | -EBUSY, /* IMX_SC_ERR_BUSY */ | ||
| 77 | -EIO, /* IMX_SC_ERR_FAIL */ | ||
| 78 | }; | ||
| 79 | |||
| 80 | static struct imx_sc_ipc *imx_sc_ipc_handle; | ||
| 81 | |||
| 82 | static inline int imx_sc_to_linux_errno(int errno) | ||
| 83 | { | ||
| 84 | if (errno >= IMX_SC_ERR_NONE && errno < IMX_SC_ERR_LAST) | ||
| 85 | return imx_sc_linux_errmap[errno]; | ||
| 86 | return -EIO; | ||
| 87 | } | ||
| 88 | |||
| 89 | /* | ||
| 90 | * Get the default handle used by SCU | ||
| 91 | */ | ||
| 92 | int imx_scu_get_handle(struct imx_sc_ipc **ipc) | ||
| 93 | { | ||
| 94 | if (!imx_sc_ipc_handle) | ||
| 95 | return -EPROBE_DEFER; | ||
| 96 | |||
| 97 | *ipc = imx_sc_ipc_handle; | ||
| 98 | return 0; | ||
| 99 | } | ||
| 100 | EXPORT_SYMBOL(imx_scu_get_handle); | ||
| 101 | |||
| 102 | static void imx_scu_rx_callback(struct mbox_client *c, void *msg) | ||
| 103 | { | ||
| 104 | struct imx_sc_chan *sc_chan = container_of(c, struct imx_sc_chan, cl); | ||
| 105 | struct imx_sc_ipc *sc_ipc = sc_chan->sc_ipc; | ||
| 106 | struct imx_sc_rpc_msg *hdr; | ||
| 107 | u32 *data = msg; | ||
| 108 | |||
| 109 | if (sc_chan->idx == 0) { | ||
| 110 | hdr = msg; | ||
| 111 | sc_ipc->rx_size = hdr->size; | ||
| 112 | dev_dbg(sc_ipc->dev, "msg rx size %u\n", sc_ipc->rx_size); | ||
| 113 | if (sc_ipc->rx_size > 4) | ||
| 114 | dev_warn(sc_ipc->dev, "RPC does not support receiving over 4 words: %u\n", | ||
| 115 | sc_ipc->rx_size); | ||
| 116 | } | ||
| 117 | |||
| 118 | sc_ipc->msg[sc_chan->idx] = *data; | ||
| 119 | sc_ipc->count++; | ||
| 120 | |||
| 121 | dev_dbg(sc_ipc->dev, "mu %u msg %u 0x%x\n", sc_chan->idx, | ||
| 122 | sc_ipc->count, *data); | ||
| 123 | |||
| 124 | if ((sc_ipc->rx_size != 0) && (sc_ipc->count == sc_ipc->rx_size)) | ||
| 125 | complete(&sc_ipc->done); | ||
| 126 | } | ||
| 127 | |||
| 128 | static int imx_scu_ipc_write(struct imx_sc_ipc *sc_ipc, void *msg) | ||
| 129 | { | ||
| 130 | struct imx_sc_rpc_msg *hdr = msg; | ||
| 131 | struct imx_sc_chan *sc_chan; | ||
| 132 | u32 *data = msg; | ||
| 133 | int ret; | ||
| 134 | int i; | ||
| 135 | |||
| 136 | /* Check size */ | ||
| 137 | if (hdr->size > IMX_SC_RPC_MAX_MSG) | ||
| 138 | return -EINVAL; | ||
| 139 | |||
| 140 | dev_dbg(sc_ipc->dev, "RPC SVC %u FUNC %u SIZE %u\n", hdr->svc, | ||
| 141 | hdr->func, hdr->size); | ||
| 142 | |||
| 143 | for (i = 0; i < hdr->size; i++) { | ||
| 144 | sc_chan = &sc_ipc->chans[i % 4]; | ||
| 145 | ret = mbox_send_message(sc_chan->ch, &data[i]); | ||
| 146 | if (ret < 0) | ||
| 147 | return ret; | ||
| 148 | } | ||
| 149 | |||
| 150 | return 0; | ||
| 151 | } | ||
| 152 | |||
| 153 | /* | ||
| 154 | * RPC command/response | ||
| 155 | */ | ||
| 156 | int imx_scu_call_rpc(struct imx_sc_ipc *sc_ipc, void *msg, bool have_resp) | ||
| 157 | { | ||
| 158 | struct imx_sc_rpc_msg *hdr; | ||
| 159 | int ret; | ||
| 160 | |||
| 161 | if (WARN_ON(!sc_ipc || !msg)) | ||
| 162 | return -EINVAL; | ||
| 163 | |||
| 164 | mutex_lock(&sc_ipc->lock); | ||
| 165 | reinit_completion(&sc_ipc->done); | ||
| 166 | |||
| 167 | sc_ipc->msg = msg; | ||
| 168 | sc_ipc->count = 0; | ||
| 169 | ret = imx_scu_ipc_write(sc_ipc, msg); | ||
| 170 | if (ret < 0) { | ||
| 171 | dev_err(sc_ipc->dev, "RPC send msg failed: %d\n", ret); | ||
| 172 | goto out; | ||
| 173 | } | ||
| 174 | |||
| 175 | if (have_resp) { | ||
| 176 | if (!wait_for_completion_timeout(&sc_ipc->done, | ||
| 177 | MAX_RX_TIMEOUT)) { | ||
| 178 | dev_err(sc_ipc->dev, "RPC send msg timeout\n"); | ||
| 179 | mutex_unlock(&sc_ipc->lock); | ||
| 180 | return -ETIMEDOUT; | ||
| 181 | } | ||
| 182 | |||
| 183 | /* response status is stored in hdr->func field */ | ||
| 184 | hdr = msg; | ||
| 185 | ret = hdr->func; | ||
| 186 | } | ||
| 187 | |||
| 188 | out: | ||
| 189 | mutex_unlock(&sc_ipc->lock); | ||
| 190 | |||
| 191 | dev_dbg(sc_ipc->dev, "RPC SVC done\n"); | ||
| 192 | |||
| 193 | return imx_sc_to_linux_errno(ret); | ||
| 194 | } | ||
| 195 | EXPORT_SYMBOL(imx_scu_call_rpc); | ||
| 196 | |||
| 197 | static int imx_scu_probe(struct platform_device *pdev) | ||
| 198 | { | ||
| 199 | struct device *dev = &pdev->dev; | ||
| 200 | struct imx_sc_ipc *sc_ipc; | ||
| 201 | struct imx_sc_chan *sc_chan; | ||
| 202 | struct mbox_client *cl; | ||
| 203 | char *chan_name; | ||
| 204 | int ret; | ||
| 205 | int i; | ||
| 206 | |||
| 207 | sc_ipc = devm_kzalloc(dev, sizeof(*sc_ipc), GFP_KERNEL); | ||
| 208 | if (!sc_ipc) | ||
| 209 | return -ENOMEM; | ||
| 210 | |||
| 211 | for (i = 0; i < SCU_MU_CHAN_NUM; i++) { | ||
| 212 | if (i < 4) | ||
| 213 | chan_name = kasprintf(GFP_KERNEL, "tx%d", i); | ||
| 214 | else | ||
| 215 | chan_name = kasprintf(GFP_KERNEL, "rx%d", i - 4); | ||
| 216 | |||
| 217 | if (!chan_name) | ||
| 218 | return -ENOMEM; | ||
| 219 | |||
| 220 | sc_chan = &sc_ipc->chans[i]; | ||
| 221 | cl = &sc_chan->cl; | ||
| 222 | cl->dev = dev; | ||
| 223 | cl->tx_block = false; | ||
| 224 | cl->knows_txdone = true; | ||
| 225 | cl->rx_callback = imx_scu_rx_callback; | ||
| 226 | |||
| 227 | sc_chan->sc_ipc = sc_ipc; | ||
| 228 | sc_chan->idx = i % 4; | ||
| 229 | sc_chan->ch = mbox_request_channel_byname(cl, chan_name); | ||
| 230 | if (IS_ERR(sc_chan->ch)) { | ||
| 231 | ret = PTR_ERR(sc_chan->ch); | ||
| 232 | if (ret != -EPROBE_DEFER) | ||
| 233 | dev_err(dev, "Failed to request mbox chan %s ret %d\n", | ||
| 234 | chan_name, ret); | ||
| 235 | return ret; | ||
| 236 | } | ||
| 237 | |||
| 238 | dev_dbg(dev, "request mbox chan %s\n", chan_name); | ||
| 239 | /* chan_name is not used anymore by framework */ | ||
| 240 | kfree(chan_name); | ||
| 241 | } | ||
| 242 | |||
| 243 | sc_ipc->dev = dev; | ||
| 244 | mutex_init(&sc_ipc->lock); | ||
| 245 | init_completion(&sc_ipc->done); | ||
| 246 | |||
| 247 | imx_sc_ipc_handle = sc_ipc; | ||
| 248 | |||
| 249 | dev_info(dev, "NXP i.MX SCU Initialized\n"); | ||
| 250 | |||
| 251 | return devm_of_platform_populate(dev); | ||
| 252 | } | ||
| 253 | |||
| 254 | static const struct of_device_id imx_scu_match[] = { | ||
| 255 | { .compatible = "fsl,imx-scu", }, | ||
| 256 | { /* Sentinel */ } | ||
| 257 | }; | ||
| 258 | |||
| 259 | static struct platform_driver imx_scu_driver = { | ||
| 260 | .driver = { | ||
| 261 | .name = "imx-scu", | ||
| 262 | .of_match_table = imx_scu_match, | ||
| 263 | }, | ||
| 264 | .probe = imx_scu_probe, | ||
| 265 | }; | ||
| 266 | builtin_platform_driver(imx_scu_driver); | ||
| 267 | |||
| 268 | MODULE_AUTHOR("Dong Aisheng <aisheng.dong@nxp.com>"); | ||
| 269 | MODULE_DESCRIPTION("IMX SCU firmware protocol driver"); | ||
| 270 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/firmware/imx/misc.c b/drivers/firmware/imx/misc.c new file mode 100644 index 000000000000..97f5424dbac9 --- /dev/null +++ b/drivers/firmware/imx/misc.c | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0+ | ||
| 2 | /* | ||
| 3 | * Copyright (C) 2016 Freescale Semiconductor, Inc. | ||
| 4 | * Copyright 2017~2018 NXP | ||
| 5 | * Author: Dong Aisheng <aisheng.dong@nxp.com> | ||
| 6 | * | ||
| 7 | * File containing client-side RPC functions for the MISC service. These | ||
| 8 | * function are ported to clients that communicate to the SC. | ||
| 9 | * | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include <linux/firmware/imx/svc/misc.h> | ||
| 13 | |||
| 14 | struct imx_sc_msg_req_misc_set_ctrl { | ||
| 15 | struct imx_sc_rpc_msg hdr; | ||
| 16 | u32 ctrl; | ||
| 17 | u32 val; | ||
| 18 | u16 resource; | ||
| 19 | } __packed; | ||
| 20 | |||
| 21 | struct imx_sc_msg_req_misc_get_ctrl { | ||
| 22 | struct imx_sc_rpc_msg hdr; | ||
| 23 | u32 ctrl; | ||
| 24 | u16 resource; | ||
| 25 | } __packed; | ||
| 26 | |||
| 27 | struct imx_sc_msg_resp_misc_get_ctrl { | ||
| 28 | struct imx_sc_rpc_msg hdr; | ||
| 29 | u32 val; | ||
| 30 | } __packed; | ||
| 31 | |||
| 32 | /* | ||
| 33 | * This function sets a miscellaneous control value. | ||
| 34 | * | ||
| 35 | * @param[in] ipc IPC handle | ||
| 36 | * @param[in] resource resource the control is associated with | ||
| 37 | * @param[in] ctrl control to change | ||
| 38 | * @param[in] val value to apply to the control | ||
| 39 | * | ||
| 40 | * @return Returns 0 for success and < 0 for errors. | ||
| 41 | */ | ||
| 42 | |||
| 43 | int imx_sc_misc_set_control(struct imx_sc_ipc *ipc, u32 resource, | ||
| 44 | u8 ctrl, u32 val) | ||
| 45 | { | ||
| 46 | struct imx_sc_msg_req_misc_set_ctrl msg; | ||
| 47 | struct imx_sc_rpc_msg *hdr = &msg.hdr; | ||
| 48 | |||
| 49 | hdr->ver = IMX_SC_RPC_VERSION; | ||
| 50 | hdr->svc = (uint8_t)IMX_SC_RPC_SVC_MISC; | ||
| 51 | hdr->func = (uint8_t)IMX_SC_MISC_FUNC_SET_CONTROL; | ||
| 52 | hdr->size = 4; | ||
| 53 | |||
| 54 | msg.ctrl = ctrl; | ||
| 55 | msg.val = val; | ||
| 56 | msg.resource = resource; | ||
| 57 | |||
| 58 | return imx_scu_call_rpc(ipc, &msg, true); | ||
| 59 | } | ||
| 60 | EXPORT_SYMBOL(imx_sc_misc_set_control); | ||
| 61 | |||
| 62 | /* | ||
| 63 | * This function gets a miscellaneous control value. | ||
| 64 | * | ||
| 65 | * @param[in] ipc IPC handle | ||
| 66 | * @param[in] resource resource the control is associated with | ||
| 67 | * @param[in] ctrl control to get | ||
| 68 | * @param[out] val pointer to return the control value | ||
| 69 | * | ||
| 70 | * @return Returns 0 for success and < 0 for errors. | ||
| 71 | */ | ||
| 72 | |||
| 73 | int imx_sc_misc_get_control(struct imx_sc_ipc *ipc, u32 resource, | ||
| 74 | u8 ctrl, u32 *val) | ||
| 75 | { | ||
| 76 | struct imx_sc_msg_req_misc_get_ctrl msg; | ||
| 77 | struct imx_sc_msg_resp_misc_get_ctrl *resp; | ||
| 78 | struct imx_sc_rpc_msg *hdr = &msg.hdr; | ||
| 79 | int ret; | ||
| 80 | |||
| 81 | hdr->ver = IMX_SC_RPC_VERSION; | ||
| 82 | hdr->svc = (uint8_t)IMX_SC_RPC_SVC_MISC; | ||
| 83 | hdr->func = (uint8_t)IMX_SC_MISC_FUNC_GET_CONTROL; | ||
| 84 | hdr->size = 3; | ||
| 85 | |||
| 86 | msg.ctrl = ctrl; | ||
| 87 | msg.resource = resource; | ||
| 88 | |||
| 89 | ret = imx_scu_call_rpc(ipc, &msg, true); | ||
| 90 | if (ret) | ||
| 91 | return ret; | ||
| 92 | |||
| 93 | resp = (struct imx_sc_msg_resp_misc_get_ctrl *)&msg; | ||
| 94 | if (val != NULL) | ||
| 95 | *val = resp->val; | ||
| 96 | |||
| 97 | return 0; | ||
| 98 | } | ||
| 99 | EXPORT_SYMBOL(imx_sc_misc_get_control); | ||
diff --git a/drivers/firmware/iscsi_ibft_find.c b/drivers/firmware/iscsi_ibft_find.c index 2224f1dc074b..72d9ea18270b 100644 --- a/drivers/firmware/iscsi_ibft_find.c +++ b/drivers/firmware/iscsi_ibft_find.c | |||
| @@ -18,7 +18,7 @@ | |||
| 18 | * GNU General Public License for more details. | 18 | * GNU General Public License for more details. |
| 19 | */ | 19 | */ |
| 20 | 20 | ||
| 21 | #include <linux/bootmem.h> | 21 | #include <linux/memblock.h> |
| 22 | #include <linux/blkdev.h> | 22 | #include <linux/blkdev.h> |
| 23 | #include <linux/ctype.h> | 23 | #include <linux/ctype.h> |
| 24 | #include <linux/device.h> | 24 | #include <linux/device.h> |
diff --git a/drivers/firmware/memmap.c b/drivers/firmware/memmap.c index 5de3ed29282c..d168c87c7d30 100644 --- a/drivers/firmware/memmap.c +++ b/drivers/firmware/memmap.c | |||
| @@ -19,7 +19,7 @@ | |||
| 19 | #include <linux/kernel.h> | 19 | #include <linux/kernel.h> |
| 20 | #include <linux/module.h> | 20 | #include <linux/module.h> |
| 21 | #include <linux/types.h> | 21 | #include <linux/types.h> |
| 22 | #include <linux/bootmem.h> | 22 | #include <linux/memblock.h> |
| 23 | #include <linux/slab.h> | 23 | #include <linux/slab.h> |
| 24 | #include <linux/mm.h> | 24 | #include <linux/mm.h> |
| 25 | 25 | ||
| @@ -333,7 +333,8 @@ int __init firmware_map_add_early(u64 start, u64 end, const char *type) | |||
| 333 | { | 333 | { |
| 334 | struct firmware_map_entry *entry; | 334 | struct firmware_map_entry *entry; |
| 335 | 335 | ||
| 336 | entry = memblock_virt_alloc(sizeof(struct firmware_map_entry), 0); | 336 | entry = memblock_alloc(sizeof(struct firmware_map_entry), |
| 337 | SMP_CACHE_BYTES); | ||
| 337 | if (WARN_ON(!entry)) | 338 | if (WARN_ON(!entry)) |
| 338 | return -ENOMEM; | 339 | return -ENOMEM; |
| 339 | 340 | ||
diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c index 0ec2ca87318c..29fbc818a573 100644 --- a/drivers/firmware/meson/meson_sm.c +++ b/drivers/firmware/meson/meson_sm.c | |||
| @@ -24,6 +24,7 @@ | |||
| 24 | #include <linux/printk.h> | 24 | #include <linux/printk.h> |
| 25 | #include <linux/types.h> | 25 | #include <linux/types.h> |
| 26 | #include <linux/sizes.h> | 26 | #include <linux/sizes.h> |
| 27 | #include <linux/slab.h> | ||
| 27 | 28 | ||
| 28 | #include <linux/firmware/meson/meson_sm.h> | 29 | #include <linux/firmware/meson/meson_sm.h> |
| 29 | 30 | ||
| @@ -48,6 +49,7 @@ struct meson_sm_chip gxbb_chip = { | |||
| 48 | CMD(SM_EFUSE_READ, 0x82000030), | 49 | CMD(SM_EFUSE_READ, 0x82000030), |
| 49 | CMD(SM_EFUSE_WRITE, 0x82000031), | 50 | CMD(SM_EFUSE_WRITE, 0x82000031), |
| 50 | CMD(SM_EFUSE_USER_MAX, 0x82000033), | 51 | CMD(SM_EFUSE_USER_MAX, 0x82000033), |
| 52 | CMD(SM_GET_CHIP_ID, 0x82000044), | ||
| 51 | { /* sentinel */ }, | 53 | { /* sentinel */ }, |
| 52 | }, | 54 | }, |
| 53 | }; | 55 | }; |
| @@ -214,6 +216,57 @@ int meson_sm_call_write(void *buffer, unsigned int size, unsigned int cmd_index, | |||
| 214 | } | 216 | } |
| 215 | EXPORT_SYMBOL(meson_sm_call_write); | 217 | EXPORT_SYMBOL(meson_sm_call_write); |
| 216 | 218 | ||
| 219 | #define SM_CHIP_ID_LENGTH 119 | ||
| 220 | #define SM_CHIP_ID_OFFSET 4 | ||
| 221 | #define SM_CHIP_ID_SIZE 12 | ||
| 222 | |||
| 223 | static ssize_t serial_show(struct device *dev, struct device_attribute *attr, | ||
| 224 | char *buf) | ||
| 225 | { | ||
| 226 | uint8_t *id_buf; | ||
| 227 | int ret; | ||
| 228 | |||
| 229 | id_buf = kmalloc(SM_CHIP_ID_LENGTH, GFP_KERNEL); | ||
| 230 | if (!id_buf) | ||
| 231 | return -ENOMEM; | ||
| 232 | |||
| 233 | ret = meson_sm_call_read(id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID, | ||
| 234 | 0, 0, 0, 0, 0); | ||
| 235 | if (ret < 0) { | ||
| 236 | kfree(id_buf); | ||
| 237 | return ret; | ||
| 238 | } | ||
| 239 | |||
| 240 | ret = sprintf(buf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", | ||
| 241 | id_buf[SM_CHIP_ID_OFFSET + 0], | ||
| 242 | id_buf[SM_CHIP_ID_OFFSET + 1], | ||
| 243 | id_buf[SM_CHIP_ID_OFFSET + 2], | ||
| 244 | id_buf[SM_CHIP_ID_OFFSET + 3], | ||
| 245 | id_buf[SM_CHIP_ID_OFFSET + 4], | ||
| 246 | id_buf[SM_CHIP_ID_OFFSET + 5], | ||
| 247 | id_buf[SM_CHIP_ID_OFFSET + 6], | ||
| 248 | id_buf[SM_CHIP_ID_OFFSET + 7], | ||
| 249 | id_buf[SM_CHIP_ID_OFFSET + 8], | ||
| 250 | id_buf[SM_CHIP_ID_OFFSET + 9], | ||
| 251 | id_buf[SM_CHIP_ID_OFFSET + 10], | ||
| 252 | id_buf[SM_CHIP_ID_OFFSET + 11]); | ||
| 253 | |||
| 254 | kfree(id_buf); | ||
| 255 | |||
| 256 | return ret; | ||
| 257 | } | ||
| 258 | |||
| 259 | static DEVICE_ATTR_RO(serial); | ||
| 260 | |||
| 261 | static struct attribute *meson_sm_sysfs_attributes[] = { | ||
| 262 | &dev_attr_serial.attr, | ||
| 263 | NULL, | ||
| 264 | }; | ||
| 265 | |||
| 266 | static const struct attribute_group meson_sm_sysfs_attr_group = { | ||
| 267 | .attrs = meson_sm_sysfs_attributes, | ||
| 268 | }; | ||
| 269 | |||
| 217 | static const struct of_device_id meson_sm_ids[] = { | 270 | static const struct of_device_id meson_sm_ids[] = { |
| 218 | { .compatible = "amlogic,meson-gxbb-sm", .data = &gxbb_chip }, | 271 | { .compatible = "amlogic,meson-gxbb-sm", .data = &gxbb_chip }, |
| 219 | { /* sentinel */ }, | 272 | { /* sentinel */ }, |
| @@ -242,6 +295,9 @@ static int __init meson_sm_probe(struct platform_device *pdev) | |||
| 242 | fw.chip = chip; | 295 | fw.chip = chip; |
| 243 | pr_info("secure-monitor enabled\n"); | 296 | pr_info("secure-monitor enabled\n"); |
| 244 | 297 | ||
| 298 | if (sysfs_create_group(&pdev->dev.kobj, &meson_sm_sysfs_attr_group)) | ||
| 299 | goto out_in_base; | ||
| 300 | |||
| 245 | return 0; | 301 | return 0; |
| 246 | 302 | ||
| 247 | out_in_base: | 303 | out_in_base: |
diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index e778af766fae..af4eee86919d 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c | |||
| @@ -525,34 +525,44 @@ static int qcom_scm_probe(struct platform_device *pdev) | |||
| 525 | return ret; | 525 | return ret; |
| 526 | 526 | ||
| 527 | clks = (unsigned long)of_device_get_match_data(&pdev->dev); | 527 | clks = (unsigned long)of_device_get_match_data(&pdev->dev); |
| 528 | if (clks & SCM_HAS_CORE_CLK) { | 528 | |
| 529 | scm->core_clk = devm_clk_get(&pdev->dev, "core"); | 529 | scm->core_clk = devm_clk_get(&pdev->dev, "core"); |
| 530 | if (IS_ERR(scm->core_clk)) { | 530 | if (IS_ERR(scm->core_clk)) { |
| 531 | if (PTR_ERR(scm->core_clk) != -EPROBE_DEFER) | 531 | if (PTR_ERR(scm->core_clk) == -EPROBE_DEFER) |
| 532 | dev_err(&pdev->dev, | 532 | return PTR_ERR(scm->core_clk); |
| 533 | "failed to acquire core clk\n"); | 533 | |
| 534 | if (clks & SCM_HAS_CORE_CLK) { | ||
| 535 | dev_err(&pdev->dev, "failed to acquire core clk\n"); | ||
| 534 | return PTR_ERR(scm->core_clk); | 536 | return PTR_ERR(scm->core_clk); |
| 535 | } | 537 | } |
| 538 | |||
| 539 | scm->core_clk = NULL; | ||
| 536 | } | 540 | } |
| 537 | 541 | ||
| 538 | if (clks & SCM_HAS_IFACE_CLK) { | 542 | scm->iface_clk = devm_clk_get(&pdev->dev, "iface"); |
| 539 | scm->iface_clk = devm_clk_get(&pdev->dev, "iface"); | 543 | if (IS_ERR(scm->iface_clk)) { |
| 540 | if (IS_ERR(scm->iface_clk)) { | 544 | if (PTR_ERR(scm->iface_clk) == -EPROBE_DEFER) |
| 541 | if (PTR_ERR(scm->iface_clk) != -EPROBE_DEFER) | 545 | return PTR_ERR(scm->iface_clk); |
| 542 | dev_err(&pdev->dev, | 546 | |
| 543 | "failed to acquire iface clk\n"); | 547 | if (clks & SCM_HAS_IFACE_CLK) { |
| 548 | dev_err(&pdev->dev, "failed to acquire iface clk\n"); | ||
| 544 | return PTR_ERR(scm->iface_clk); | 549 | return PTR_ERR(scm->iface_clk); |
| 545 | } | 550 | } |
| 551 | |||
| 552 | scm->iface_clk = NULL; | ||
| 546 | } | 553 | } |
| 547 | 554 | ||
| 548 | if (clks & SCM_HAS_BUS_CLK) { | 555 | scm->bus_clk = devm_clk_get(&pdev->dev, "bus"); |
| 549 | scm->bus_clk = devm_clk_get(&pdev->dev, "bus"); | 556 | if (IS_ERR(scm->bus_clk)) { |
| 550 | if (IS_ERR(scm->bus_clk)) { | 557 | if (PTR_ERR(scm->bus_clk) == -EPROBE_DEFER) |
| 551 | if (PTR_ERR(scm->bus_clk) != -EPROBE_DEFER) | 558 | return PTR_ERR(scm->bus_clk); |
| 552 | dev_err(&pdev->dev, | 559 | |
| 553 | "failed to acquire bus clk\n"); | 560 | if (clks & SCM_HAS_BUS_CLK) { |
| 561 | dev_err(&pdev->dev, "failed to acquire bus clk\n"); | ||
| 554 | return PTR_ERR(scm->bus_clk); | 562 | return PTR_ERR(scm->bus_clk); |
| 555 | } | 563 | } |
| 564 | |||
| 565 | scm->bus_clk = NULL; | ||
| 556 | } | 566 | } |
| 557 | 567 | ||
| 558 | scm->reset.ops = &qcom_scm_pas_reset_ops; | 568 | scm->reset.ops = &qcom_scm_pas_reset_ops; |
| @@ -594,23 +604,23 @@ static const struct of_device_id qcom_scm_dt_match[] = { | |||
| 594 | { .compatible = "qcom,scm-apq8064", | 604 | { .compatible = "qcom,scm-apq8064", |
| 595 | /* FIXME: This should have .data = (void *) SCM_HAS_CORE_CLK */ | 605 | /* FIXME: This should have .data = (void *) SCM_HAS_CORE_CLK */ |
| 596 | }, | 606 | }, |
| 597 | { .compatible = "qcom,scm-msm8660", | 607 | { .compatible = "qcom,scm-apq8084", .data = (void *)(SCM_HAS_CORE_CLK | |
| 598 | .data = (void *) SCM_HAS_CORE_CLK, | 608 | SCM_HAS_IFACE_CLK | |
| 599 | }, | 609 | SCM_HAS_BUS_CLK) |
| 600 | { .compatible = "qcom,scm-msm8960", | ||
| 601 | .data = (void *) SCM_HAS_CORE_CLK, | ||
| 602 | }, | ||
| 603 | { .compatible = "qcom,scm-msm8996", | ||
| 604 | .data = NULL, /* no clocks */ | ||
| 605 | }, | 610 | }, |
| 606 | { .compatible = "qcom,scm-ipq4019", | 611 | { .compatible = "qcom,scm-ipq4019" }, |
| 607 | .data = NULL, /* no clocks */ | 612 | { .compatible = "qcom,scm-msm8660", .data = (void *) SCM_HAS_CORE_CLK }, |
| 613 | { .compatible = "qcom,scm-msm8960", .data = (void *) SCM_HAS_CORE_CLK }, | ||
| 614 | { .compatible = "qcom,scm-msm8916", .data = (void *)(SCM_HAS_CORE_CLK | | ||
| 615 | SCM_HAS_IFACE_CLK | | ||
| 616 | SCM_HAS_BUS_CLK) | ||
| 608 | }, | 617 | }, |
| 609 | { .compatible = "qcom,scm", | 618 | { .compatible = "qcom,scm-msm8974", .data = (void *)(SCM_HAS_CORE_CLK | |
| 610 | .data = (void *)(SCM_HAS_CORE_CLK | 619 | SCM_HAS_IFACE_CLK | |
| 611 | | SCM_HAS_IFACE_CLK | 620 | SCM_HAS_BUS_CLK) |
| 612 | | SCM_HAS_BUS_CLK), | ||
| 613 | }, | 621 | }, |
| 622 | { .compatible = "qcom,scm-msm8996" }, | ||
| 623 | { .compatible = "qcom,scm" }, | ||
| 614 | {} | 624 | {} |
| 615 | }; | 625 | }; |
| 616 | 626 | ||
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 14a456afa379..a3d5b518c10e 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #include <linux/of_address.h> | 18 | #include <linux/of_address.h> |
| 19 | #include <linux/of_device.h> | 19 | #include <linux/of_device.h> |
| 20 | #include <linux/platform_device.h> | 20 | #include <linux/platform_device.h> |
| 21 | #include <linux/pm.h> | ||
| 21 | #include <linux/semaphore.h> | 22 | #include <linux/semaphore.h> |
| 22 | #include <linux/sched/clock.h> | 23 | #include <linux/sched/clock.h> |
| 23 | 24 | ||
| @@ -843,6 +844,23 @@ free_tx: | |||
| 843 | return err; | 844 | return err; |
| 844 | } | 845 | } |
| 845 | 846 | ||
| 847 | static int __maybe_unused tegra_bpmp_resume(struct device *dev) | ||
| 848 | { | ||
| 849 | struct tegra_bpmp *bpmp = dev_get_drvdata(dev); | ||
| 850 | unsigned int i; | ||
| 851 | |||
| 852 | /* reset message channels */ | ||
| 853 | tegra_bpmp_channel_reset(bpmp->tx_channel); | ||
| 854 | tegra_bpmp_channel_reset(bpmp->rx_channel); | ||
| 855 | |||
| 856 | for (i = 0; i < bpmp->threaded.count; i++) | ||
| 857 | tegra_bpmp_channel_reset(&bpmp->threaded_channels[i]); | ||
| 858 | |||
| 859 | return 0; | ||
| 860 | } | ||
| 861 | |||
| 862 | static SIMPLE_DEV_PM_OPS(tegra_bpmp_pm_ops, NULL, tegra_bpmp_resume); | ||
| 863 | |||
| 846 | static const struct tegra_bpmp_soc tegra186_soc = { | 864 | static const struct tegra_bpmp_soc tegra186_soc = { |
| 847 | .channels = { | 865 | .channels = { |
| 848 | .cpu_tx = { | 866 | .cpu_tx = { |
| @@ -871,6 +889,7 @@ static struct platform_driver tegra_bpmp_driver = { | |||
| 871 | .driver = { | 889 | .driver = { |
| 872 | .name = "tegra-bpmp", | 890 | .name = "tegra-bpmp", |
| 873 | .of_match_table = tegra_bpmp_match, | 891 | .of_match_table = tegra_bpmp_match, |
| 892 | .pm = &tegra_bpmp_pm_ops, | ||
| 874 | }, | 893 | }, |
| 875 | .probe = tegra_bpmp_probe, | 894 | .probe = tegra_bpmp_probe, |
| 876 | }; | 895 | }; |
diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index 7fa744793bc5..69ed1464175c 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c | |||
| @@ -66,14 +66,14 @@ struct ti_sci_xfers_info { | |||
| 66 | 66 | ||
| 67 | /** | 67 | /** |
| 68 | * struct ti_sci_desc - Description of SoC integration | 68 | * struct ti_sci_desc - Description of SoC integration |
| 69 | * @host_id: Host identifier representing the compute entity | 69 | * @default_host_id: Host identifier representing the compute entity |
| 70 | * @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds) | 70 | * @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds) |
| 71 | * @max_msgs: Maximum number of messages that can be pending | 71 | * @max_msgs: Maximum number of messages that can be pending |
| 72 | * simultaneously in the system | 72 | * simultaneously in the system |
| 73 | * @max_msg_size: Maximum size of data per message that can be handled. | 73 | * @max_msg_size: Maximum size of data per message that can be handled. |
| 74 | */ | 74 | */ |
| 75 | struct ti_sci_desc { | 75 | struct ti_sci_desc { |
| 76 | u8 host_id; | 76 | u8 default_host_id; |
| 77 | int max_rx_timeout_ms; | 77 | int max_rx_timeout_ms; |
| 78 | int max_msgs; | 78 | int max_msgs; |
| 79 | int max_msg_size; | 79 | int max_msg_size; |
| @@ -94,6 +94,7 @@ struct ti_sci_desc { | |||
| 94 | * @chan_rx: Receive mailbox channel | 94 | * @chan_rx: Receive mailbox channel |
| 95 | * @minfo: Message info | 95 | * @minfo: Message info |
| 96 | * @node: list head | 96 | * @node: list head |
| 97 | * @host_id: Host ID | ||
| 97 | * @users: Number of users of this instance | 98 | * @users: Number of users of this instance |
| 98 | */ | 99 | */ |
| 99 | struct ti_sci_info { | 100 | struct ti_sci_info { |
| @@ -110,6 +111,7 @@ struct ti_sci_info { | |||
| 110 | struct mbox_chan *chan_rx; | 111 | struct mbox_chan *chan_rx; |
| 111 | struct ti_sci_xfers_info minfo; | 112 | struct ti_sci_xfers_info minfo; |
| 112 | struct list_head node; | 113 | struct list_head node; |
| 114 | u8 host_id; | ||
| 113 | /* protected by ti_sci_list_mutex */ | 115 | /* protected by ti_sci_list_mutex */ |
| 114 | int users; | 116 | int users; |
| 115 | 117 | ||
| @@ -370,7 +372,7 @@ static struct ti_sci_xfer *ti_sci_get_one_xfer(struct ti_sci_info *info, | |||
| 370 | 372 | ||
| 371 | hdr->seq = xfer_id; | 373 | hdr->seq = xfer_id; |
| 372 | hdr->type = msg_type; | 374 | hdr->type = msg_type; |
| 373 | hdr->host = info->desc->host_id; | 375 | hdr->host = info->host_id; |
| 374 | hdr->flags = msg_flags; | 376 | hdr->flags = msg_flags; |
| 375 | 377 | ||
| 376 | return xfer; | 378 | return xfer; |
| @@ -1793,7 +1795,7 @@ static int tisci_reboot_handler(struct notifier_block *nb, unsigned long mode, | |||
| 1793 | 1795 | ||
| 1794 | /* Description for K2G */ | 1796 | /* Description for K2G */ |
| 1795 | static const struct ti_sci_desc ti_sci_pmmc_k2g_desc = { | 1797 | static const struct ti_sci_desc ti_sci_pmmc_k2g_desc = { |
| 1796 | .host_id = 2, | 1798 | .default_host_id = 2, |
| 1797 | /* Conservative duration */ | 1799 | /* Conservative duration */ |
| 1798 | .max_rx_timeout_ms = 1000, | 1800 | .max_rx_timeout_ms = 1000, |
| 1799 | /* Limited by MBOX_TX_QUEUE_LEN. K2G can handle upto 128 messages! */ | 1801 | /* Limited by MBOX_TX_QUEUE_LEN. K2G can handle upto 128 messages! */ |
| @@ -1819,6 +1821,7 @@ static int ti_sci_probe(struct platform_device *pdev) | |||
| 1819 | int ret = -EINVAL; | 1821 | int ret = -EINVAL; |
| 1820 | int i; | 1822 | int i; |
| 1821 | int reboot = 0; | 1823 | int reboot = 0; |
| 1824 | u32 h_id; | ||
| 1822 | 1825 | ||
| 1823 | of_id = of_match_device(ti_sci_of_match, dev); | 1826 | of_id = of_match_device(ti_sci_of_match, dev); |
| 1824 | if (!of_id) { | 1827 | if (!of_id) { |
| @@ -1833,6 +1836,19 @@ static int ti_sci_probe(struct platform_device *pdev) | |||
| 1833 | 1836 | ||
| 1834 | info->dev = dev; | 1837 | info->dev = dev; |
| 1835 | info->desc = desc; | 1838 | info->desc = desc; |
| 1839 | ret = of_property_read_u32(dev->of_node, "ti,host-id", &h_id); | ||
| 1840 | /* if the property is not present in DT, use a default from desc */ | ||
| 1841 | if (ret < 0) { | ||
| 1842 | info->host_id = info->desc->default_host_id; | ||
| 1843 | } else { | ||
| 1844 | if (!h_id) { | ||
| 1845 | dev_warn(dev, "Host ID 0 is reserved for firmware\n"); | ||
| 1846 | info->host_id = info->desc->default_host_id; | ||
| 1847 | } else { | ||
| 1848 | info->host_id = h_id; | ||
| 1849 | } | ||
| 1850 | } | ||
| 1851 | |||
| 1836 | reboot = of_property_read_bool(dev->of_node, | 1852 | reboot = of_property_read_bool(dev->of_node, |
| 1837 | "ti,system-reboot-controller"); | 1853 | "ti,system-reboot-controller"); |
| 1838 | INIT_LIST_HEAD(&info->node); | 1854 | INIT_LIST_HEAD(&info->node); |
diff --git a/drivers/firmware/xilinx/Kconfig b/drivers/firmware/xilinx/Kconfig new file mode 100644 index 000000000000..8f44b9cd295a --- /dev/null +++ b/drivers/firmware/xilinx/Kconfig | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0 | ||
| 2 | # Kconfig for Xilinx firmwares | ||
| 3 | |||
| 4 | menu "Zynq MPSoC Firmware Drivers" | ||
| 5 | depends on ARCH_ZYNQMP | ||
| 6 | |||
| 7 | config ZYNQMP_FIRMWARE | ||
| 8 | bool "Enable Xilinx Zynq MPSoC firmware interface" | ||
| 9 | help | ||
| 10 | Firmware interface driver is used by different | ||
| 11 | drivers to communicate with the firmware for | ||
| 12 | various platform management services. | ||
| 13 | Say yes to enable ZynqMP firmware interface driver. | ||
| 14 | If in doubt, say N. | ||
| 15 | |||
| 16 | config ZYNQMP_FIRMWARE_DEBUG | ||
| 17 | bool "Enable Xilinx Zynq MPSoC firmware debug APIs" | ||
| 18 | depends on ZYNQMP_FIRMWARE && DEBUG_FS | ||
| 19 | help | ||
| 20 | Say yes to enable ZynqMP firmware interface debug APIs. | ||
| 21 | If in doubt, say N. | ||
| 22 | |||
| 23 | endmenu | ||
diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile new file mode 100644 index 000000000000..875a53703c82 --- /dev/null +++ b/drivers/firmware/xilinx/Makefile | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0 | ||
| 2 | # Makefile for Xilinx firmwares | ||
| 3 | |||
| 4 | obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o | ||
| 5 | obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o | ||
diff --git a/drivers/firmware/xilinx/zynqmp-debug.c b/drivers/firmware/xilinx/zynqmp-debug.c new file mode 100644 index 000000000000..2771df6df379 --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-debug.c | |||
| @@ -0,0 +1,250 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Xilinx Zynq MPSoC Firmware layer for debugfs APIs | ||
| 4 | * | ||
| 5 | * Copyright (C) 2014-2018 Xilinx, Inc. | ||
| 6 | * | ||
| 7 | * Michal Simek <michal.simek@xilinx.com> | ||
| 8 | * Davorin Mista <davorin.mista@aggios.com> | ||
| 9 | * Jolly Shah <jollys@xilinx.com> | ||
| 10 | * Rajan Vaja <rajanv@xilinx.com> | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include <linux/compiler.h> | ||
| 14 | #include <linux/module.h> | ||
| 15 | #include <linux/slab.h> | ||
| 16 | #include <linux/debugfs.h> | ||
| 17 | #include <linux/uaccess.h> | ||
| 18 | |||
| 19 | #include <linux/firmware/xlnx-zynqmp.h> | ||
| 20 | #include "zynqmp-debug.h" | ||
| 21 | |||
| 22 | #define PM_API_NAME_LEN 50 | ||
| 23 | |||
| 24 | struct pm_api_info { | ||
| 25 | u32 api_id; | ||
| 26 | char api_name[PM_API_NAME_LEN]; | ||
| 27 | char api_name_len; | ||
| 28 | }; | ||
| 29 | |||
| 30 | static char debugfs_buf[PAGE_SIZE]; | ||
| 31 | |||
| 32 | #define PM_API(id) {id, #id, strlen(#id)} | ||
| 33 | static struct pm_api_info pm_api_list[] = { | ||
| 34 | PM_API(PM_GET_API_VERSION), | ||
| 35 | PM_API(PM_QUERY_DATA), | ||
| 36 | }; | ||
| 37 | |||
| 38 | struct dentry *firmware_debugfs_root; | ||
| 39 | |||
| 40 | /** | ||
| 41 | * zynqmp_pm_argument_value() - Extract argument value from a PM-API request | ||
| 42 | * @arg: Entered PM-API argument in string format | ||
| 43 | * | ||
| 44 | * Return: Argument value in unsigned integer format on success | ||
| 45 | * 0 otherwise | ||
| 46 | */ | ||
| 47 | static u64 zynqmp_pm_argument_value(char *arg) | ||
| 48 | { | ||
| 49 | u64 value; | ||
| 50 | |||
| 51 | if (!arg) | ||
| 52 | return 0; | ||
| 53 | |||
| 54 | if (!kstrtou64(arg, 0, &value)) | ||
| 55 | return value; | ||
| 56 | |||
| 57 | return 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | /** | ||
| 61 | * get_pm_api_id() - Extract API-ID from a PM-API request | ||
| 62 | * @pm_api_req: Entered PM-API argument in string format | ||
| 63 | * @pm_id: API-ID | ||
| 64 | * | ||
| 65 | * Return: 0 on success else error code | ||
| 66 | */ | ||
| 67 | static int get_pm_api_id(char *pm_api_req, u32 *pm_id) | ||
| 68 | { | ||
| 69 | int i; | ||
| 70 | |||
| 71 | for (i = 0; i < ARRAY_SIZE(pm_api_list) ; i++) { | ||
| 72 | if (!strncasecmp(pm_api_req, pm_api_list[i].api_name, | ||
| 73 | pm_api_list[i].api_name_len)) { | ||
| 74 | *pm_id = pm_api_list[i].api_id; | ||
| 75 | break; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | /* If no name was entered look for PM-API ID instead */ | ||
| 80 | if (i == ARRAY_SIZE(pm_api_list) && kstrtouint(pm_api_req, 10, pm_id)) | ||
| 81 | return -EINVAL; | ||
| 82 | |||
| 83 | return 0; | ||
| 84 | } | ||
| 85 | |||
| 86 | static int process_api_request(u32 pm_id, u64 *pm_api_arg, u32 *pm_api_ret) | ||
| 87 | { | ||
| 88 | const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); | ||
| 89 | u32 pm_api_version; | ||
| 90 | int ret; | ||
| 91 | struct zynqmp_pm_query_data qdata = {0}; | ||
| 92 | |||
| 93 | if (!eemi_ops) | ||
| 94 | return -ENXIO; | ||
| 95 | |||
| 96 | switch (pm_id) { | ||
| 97 | case PM_GET_API_VERSION: | ||
| 98 | ret = eemi_ops->get_api_version(&pm_api_version); | ||
| 99 | sprintf(debugfs_buf, "PM-API Version = %d.%d\n", | ||
| 100 | pm_api_version >> 16, pm_api_version & 0xffff); | ||
| 101 | break; | ||
| 102 | case PM_QUERY_DATA: | ||
| 103 | qdata.qid = pm_api_arg[0]; | ||
| 104 | qdata.arg1 = pm_api_arg[1]; | ||
| 105 | qdata.arg2 = pm_api_arg[2]; | ||
| 106 | qdata.arg3 = pm_api_arg[3]; | ||
| 107 | |||
| 108 | ret = eemi_ops->query_data(qdata, pm_api_ret); | ||
| 109 | if (ret) | ||
| 110 | break; | ||
| 111 | |||
| 112 | switch (qdata.qid) { | ||
| 113 | case PM_QID_CLOCK_GET_NAME: | ||
| 114 | sprintf(debugfs_buf, "Clock name = %s\n", | ||
| 115 | (char *)pm_api_ret); | ||
| 116 | break; | ||
| 117 | case PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS: | ||
| 118 | sprintf(debugfs_buf, "Multiplier = %d, Divider = %d\n", | ||
| 119 | pm_api_ret[1], pm_api_ret[2]); | ||
| 120 | break; | ||
| 121 | default: | ||
| 122 | sprintf(debugfs_buf, | ||
| 123 | "data[0] = 0x%08x\ndata[1] = 0x%08x\n data[2] = 0x%08x\ndata[3] = 0x%08x\n", | ||
| 124 | pm_api_ret[0], pm_api_ret[1], | ||
| 125 | pm_api_ret[2], pm_api_ret[3]); | ||
| 126 | } | ||
| 127 | break; | ||
| 128 | default: | ||
| 129 | sprintf(debugfs_buf, "Unsupported PM-API request\n"); | ||
| 130 | ret = -EINVAL; | ||
| 131 | } | ||
| 132 | |||
| 133 | return ret; | ||
| 134 | } | ||
| 135 | |||
| 136 | /** | ||
| 137 | * zynqmp_pm_debugfs_api_write() - debugfs write function | ||
| 138 | * @file: User file | ||
| 139 | * @ptr: User entered PM-API string | ||
| 140 | * @len: Length of the userspace buffer | ||
| 141 | * @off: Offset within the file | ||
| 142 | * | ||
| 143 | * Used for triggering pm api functions by writing | ||
| 144 | * echo <pm_api_id> > /sys/kernel/debug/zynqmp_pm/power or | ||
| 145 | * echo <pm_api_name> > /sys/kernel/debug/zynqmp_pm/power | ||
| 146 | * | ||
| 147 | * Return: Number of bytes copied if PM-API request succeeds, | ||
| 148 | * the corresponding error code otherwise | ||
| 149 | */ | ||
| 150 | static ssize_t zynqmp_pm_debugfs_api_write(struct file *file, | ||
| 151 | const char __user *ptr, size_t len, | ||
| 152 | loff_t *off) | ||
| 153 | { | ||
| 154 | char *kern_buff, *tmp_buff; | ||
| 155 | char *pm_api_req; | ||
| 156 | u32 pm_id = 0; | ||
| 157 | u64 pm_api_arg[4] = {0, 0, 0, 0}; | ||
| 158 | /* Return values from PM APIs calls */ | ||
| 159 | u32 pm_api_ret[4] = {0, 0, 0, 0}; | ||
| 160 | |||
| 161 | int ret; | ||
| 162 | int i = 0; | ||
| 163 | |||
| 164 | strcpy(debugfs_buf, ""); | ||
| 165 | |||
| 166 | if (*off != 0 || len == 0) | ||
| 167 | return -EINVAL; | ||
| 168 | |||
| 169 | kern_buff = kzalloc(len, GFP_KERNEL); | ||
| 170 | if (!kern_buff) | ||
| 171 | return -ENOMEM; | ||
| 172 | |||
| 173 | tmp_buff = kern_buff; | ||
| 174 | |||
| 175 | ret = strncpy_from_user(kern_buff, ptr, len); | ||
| 176 | if (ret < 0) { | ||
| 177 | ret = -EFAULT; | ||
| 178 | goto err; | ||
| 179 | } | ||
| 180 | |||
| 181 | /* Read the API name from a user request */ | ||
| 182 | pm_api_req = strsep(&kern_buff, " "); | ||
| 183 | |||
| 184 | ret = get_pm_api_id(pm_api_req, &pm_id); | ||
| 185 | if (ret < 0) | ||
| 186 | goto err; | ||
| 187 | |||
| 188 | /* Read node_id and arguments from the PM-API request */ | ||
| 189 | pm_api_req = strsep(&kern_buff, " "); | ||
| 190 | while ((i < ARRAY_SIZE(pm_api_arg)) && pm_api_req) { | ||
| 191 | pm_api_arg[i++] = zynqmp_pm_argument_value(pm_api_req); | ||
| 192 | pm_api_req = strsep(&kern_buff, " "); | ||
| 193 | } | ||
| 194 | |||
| 195 | ret = process_api_request(pm_id, pm_api_arg, pm_api_ret); | ||
| 196 | |||
| 197 | err: | ||
| 198 | kfree(tmp_buff); | ||
| 199 | if (ret) | ||
| 200 | return ret; | ||
| 201 | |||
| 202 | return len; | ||
| 203 | } | ||
| 204 | |||
| 205 | /** | ||
| 206 | * zynqmp_pm_debugfs_api_read() - debugfs read function | ||
| 207 | * @file: User file | ||
| 208 | * @ptr: Requested pm_api_version string | ||
| 209 | * @len: Length of the userspace buffer | ||
| 210 | * @off: Offset within the file | ||
| 211 | * | ||
| 212 | * Return: Length of the version string on success | ||
| 213 | * else error code | ||
| 214 | */ | ||
| 215 | static ssize_t zynqmp_pm_debugfs_api_read(struct file *file, char __user *ptr, | ||
| 216 | size_t len, loff_t *off) | ||
| 217 | { | ||
| 218 | return simple_read_from_buffer(ptr, len, off, debugfs_buf, | ||
| 219 | strlen(debugfs_buf)); | ||
| 220 | } | ||
| 221 | |||
| 222 | /* Setup debugfs fops */ | ||
| 223 | static const struct file_operations fops_zynqmp_pm_dbgfs = { | ||
| 224 | .owner = THIS_MODULE, | ||
| 225 | .write = zynqmp_pm_debugfs_api_write, | ||
| 226 | .read = zynqmp_pm_debugfs_api_read, | ||
| 227 | }; | ||
| 228 | |||
| 229 | /** | ||
| 230 | * zynqmp_pm_api_debugfs_init - Initialize debugfs interface | ||
| 231 | * | ||
| 232 | * Return: None | ||
| 233 | */ | ||
| 234 | void zynqmp_pm_api_debugfs_init(void) | ||
| 235 | { | ||
| 236 | /* Initialize debugfs interface */ | ||
| 237 | firmware_debugfs_root = debugfs_create_dir("zynqmp-firmware", NULL); | ||
| 238 | debugfs_create_file("pm", 0660, firmware_debugfs_root, NULL, | ||
| 239 | &fops_zynqmp_pm_dbgfs); | ||
| 240 | } | ||
| 241 | |||
| 242 | /** | ||
| 243 | * zynqmp_pm_api_debugfs_exit - Remove debugfs interface | ||
| 244 | * | ||
| 245 | * Return: None | ||
| 246 | */ | ||
| 247 | void zynqmp_pm_api_debugfs_exit(void) | ||
| 248 | { | ||
| 249 | debugfs_remove_recursive(firmware_debugfs_root); | ||
| 250 | } | ||
diff --git a/drivers/firmware/xilinx/zynqmp-debug.h b/drivers/firmware/xilinx/zynqmp-debug.h new file mode 100644 index 000000000000..9929f8b433f5 --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-debug.h | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0 */ | ||
| 2 | /* | ||
| 3 | * Xilinx Zynq MPSoC Firmware layer | ||
| 4 | * | ||
| 5 | * Copyright (C) 2014-2018 Xilinx | ||
| 6 | * | ||
| 7 | * Michal Simek <michal.simek@xilinx.com> | ||
| 8 | * Davorin Mista <davorin.mista@aggios.com> | ||
| 9 | * Jolly Shah <jollys@xilinx.com> | ||
| 10 | * Rajan Vaja <rajanv@xilinx.com> | ||
| 11 | */ | ||
| 12 | |||
| 13 | #ifndef __FIRMWARE_ZYNQMP_DEBUG_H__ | ||
| 14 | #define __FIRMWARE_ZYNQMP_DEBUG_H__ | ||
| 15 | |||
| 16 | #if IS_REACHABLE(CONFIG_ZYNQMP_FIRMWARE_DEBUG) | ||
| 17 | void zynqmp_pm_api_debugfs_init(void); | ||
| 18 | void zynqmp_pm_api_debugfs_exit(void); | ||
| 19 | #else | ||
| 20 | static inline void zynqmp_pm_api_debugfs_init(void) { } | ||
| 21 | static inline void zynqmp_pm_api_debugfs_exit(void) { } | ||
| 22 | #endif | ||
| 23 | |||
| 24 | #endif /* __FIRMWARE_ZYNQMP_DEBUG_H__ */ | ||
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c new file mode 100644 index 000000000000..9a1c72a9280f --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp.c | |||
| @@ -0,0 +1,565 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Xilinx Zynq MPSoC Firmware layer | ||
| 4 | * | ||
| 5 | * Copyright (C) 2014-2018 Xilinx, Inc. | ||
| 6 | * | ||
| 7 | * Michal Simek <michal.simek@xilinx.com> | ||
| 8 | * Davorin Mista <davorin.mista@aggios.com> | ||
| 9 | * Jolly Shah <jollys@xilinx.com> | ||
| 10 | * Rajan Vaja <rajanv@xilinx.com> | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include <linux/arm-smccc.h> | ||
| 14 | #include <linux/compiler.h> | ||
| 15 | #include <linux/device.h> | ||
| 16 | #include <linux/init.h> | ||
| 17 | #include <linux/module.h> | ||
| 18 | #include <linux/of.h> | ||
| 19 | #include <linux/of_platform.h> | ||
| 20 | #include <linux/slab.h> | ||
| 21 | #include <linux/uaccess.h> | ||
| 22 | |||
| 23 | #include <linux/firmware/xlnx-zynqmp.h> | ||
| 24 | #include "zynqmp-debug.h" | ||
| 25 | |||
| 26 | /** | ||
| 27 | * zynqmp_pm_ret_code() - Convert PMU-FW error codes to Linux error codes | ||
| 28 | * @ret_status: PMUFW return code | ||
| 29 | * | ||
| 30 | * Return: corresponding Linux error code | ||
| 31 | */ | ||
| 32 | static int zynqmp_pm_ret_code(u32 ret_status) | ||
| 33 | { | ||
| 34 | switch (ret_status) { | ||
| 35 | case XST_PM_SUCCESS: | ||
| 36 | case XST_PM_DOUBLE_REQ: | ||
| 37 | return 0; | ||
| 38 | case XST_PM_NO_ACCESS: | ||
| 39 | return -EACCES; | ||
| 40 | case XST_PM_ABORT_SUSPEND: | ||
| 41 | return -ECANCELED; | ||
| 42 | case XST_PM_INTERNAL: | ||
| 43 | case XST_PM_CONFLICT: | ||
| 44 | case XST_PM_INVALID_NODE: | ||
| 45 | default: | ||
| 46 | return -EINVAL; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | static noinline int do_fw_call_fail(u64 arg0, u64 arg1, u64 arg2, | ||
| 51 | u32 *ret_payload) | ||
| 52 | { | ||
| 53 | return -ENODEV; | ||
| 54 | } | ||
| 55 | |||
| 56 | /* | ||
| 57 | * PM function call wrapper | ||
| 58 | * Invoke do_fw_call_smc or do_fw_call_hvc, depending on the configuration | ||
| 59 | */ | ||
| 60 | static int (*do_fw_call)(u64, u64, u64, u32 *ret_payload) = do_fw_call_fail; | ||
| 61 | |||
| 62 | /** | ||
| 63 | * do_fw_call_smc() - Call system-level platform management layer (SMC) | ||
| 64 | * @arg0: Argument 0 to SMC call | ||
| 65 | * @arg1: Argument 1 to SMC call | ||
| 66 | * @arg2: Argument 2 to SMC call | ||
| 67 | * @ret_payload: Returned value array | ||
| 68 | * | ||
| 69 | * Invoke platform management function via SMC call (no hypervisor present). | ||
| 70 | * | ||
| 71 | * Return: Returns status, either success or error+reason | ||
| 72 | */ | ||
| 73 | static noinline int do_fw_call_smc(u64 arg0, u64 arg1, u64 arg2, | ||
| 74 | u32 *ret_payload) | ||
| 75 | { | ||
| 76 | struct arm_smccc_res res; | ||
| 77 | |||
| 78 | arm_smccc_smc(arg0, arg1, arg2, 0, 0, 0, 0, 0, &res); | ||
| 79 | |||
| 80 | if (ret_payload) { | ||
| 81 | ret_payload[0] = lower_32_bits(res.a0); | ||
| 82 | ret_payload[1] = upper_32_bits(res.a0); | ||
| 83 | ret_payload[2] = lower_32_bits(res.a1); | ||
| 84 | ret_payload[3] = upper_32_bits(res.a1); | ||
| 85 | } | ||
| 86 | |||
| 87 | return zynqmp_pm_ret_code((enum pm_ret_status)res.a0); | ||
| 88 | } | ||
| 89 | |||
| 90 | /** | ||
| 91 | * do_fw_call_hvc() - Call system-level platform management layer (HVC) | ||
| 92 | * @arg0: Argument 0 to HVC call | ||
| 93 | * @arg1: Argument 1 to HVC call | ||
| 94 | * @arg2: Argument 2 to HVC call | ||
| 95 | * @ret_payload: Returned value array | ||
| 96 | * | ||
| 97 | * Invoke platform management function via HVC | ||
| 98 | * HVC-based for communication through hypervisor | ||
| 99 | * (no direct communication with ATF). | ||
| 100 | * | ||
| 101 | * Return: Returns status, either success or error+reason | ||
| 102 | */ | ||
| 103 | static noinline int do_fw_call_hvc(u64 arg0, u64 arg1, u64 arg2, | ||
| 104 | u32 *ret_payload) | ||
| 105 | { | ||
| 106 | struct arm_smccc_res res; | ||
| 107 | |||
| 108 | arm_smccc_hvc(arg0, arg1, arg2, 0, 0, 0, 0, 0, &res); | ||
| 109 | |||
| 110 | if (ret_payload) { | ||
| 111 | ret_payload[0] = lower_32_bits(res.a0); | ||
| 112 | ret_payload[1] = upper_32_bits(res.a0); | ||
| 113 | ret_payload[2] = lower_32_bits(res.a1); | ||
| 114 | ret_payload[3] = upper_32_bits(res.a1); | ||
| 115 | } | ||
| 116 | |||
| 117 | return zynqmp_pm_ret_code((enum pm_ret_status)res.a0); | ||
| 118 | } | ||
| 119 | |||
| 120 | /** | ||
| 121 | * zynqmp_pm_invoke_fn() - Invoke the system-level platform management layer | ||
| 122 | * caller function depending on the configuration | ||
| 123 | * @pm_api_id: Requested PM-API call | ||
| 124 | * @arg0: Argument 0 to requested PM-API call | ||
| 125 | * @arg1: Argument 1 to requested PM-API call | ||
| 126 | * @arg2: Argument 2 to requested PM-API call | ||
| 127 | * @arg3: Argument 3 to requested PM-API call | ||
| 128 | * @ret_payload: Returned value array | ||
| 129 | * | ||
| 130 | * Invoke platform management function for SMC or HVC call, depending on | ||
| 131 | * configuration. | ||
| 132 | * Following SMC Calling Convention (SMCCC) for SMC64: | ||
| 133 | * Pm Function Identifier, | ||
| 134 | * PM_SIP_SVC + PM_API_ID = | ||
| 135 | * ((SMC_TYPE_FAST << FUNCID_TYPE_SHIFT) | ||
| 136 | * ((SMC_64) << FUNCID_CC_SHIFT) | ||
| 137 | * ((SIP_START) << FUNCID_OEN_SHIFT) | ||
| 138 | * ((PM_API_ID) & FUNCID_NUM_MASK)) | ||
| 139 | * | ||
| 140 | * PM_SIP_SVC - Registered ZynqMP SIP Service Call. | ||
| 141 | * PM_API_ID - Platform Management API ID. | ||
| 142 | * | ||
| 143 | * Return: Returns status, either success or error+reason | ||
| 144 | */ | ||
| 145 | int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 arg0, u32 arg1, | ||
| 146 | u32 arg2, u32 arg3, u32 *ret_payload) | ||
| 147 | { | ||
| 148 | /* | ||
| 149 | * Added SIP service call Function Identifier | ||
| 150 | * Make sure to stay in x0 register | ||
| 151 | */ | ||
| 152 | u64 smc_arg[4]; | ||
| 153 | |||
| 154 | smc_arg[0] = PM_SIP_SVC | pm_api_id; | ||
| 155 | smc_arg[1] = ((u64)arg1 << 32) | arg0; | ||
| 156 | smc_arg[2] = ((u64)arg3 << 32) | arg2; | ||
| 157 | |||
| 158 | return do_fw_call(smc_arg[0], smc_arg[1], smc_arg[2], ret_payload); | ||
| 159 | } | ||
| 160 | |||
| 161 | static u32 pm_api_version; | ||
| 162 | static u32 pm_tz_version; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * zynqmp_pm_get_api_version() - Get version number of PMU PM firmware | ||
| 166 | * @version: Returned version value | ||
| 167 | * | ||
| 168 | * Return: Returns status, either success or error+reason | ||
| 169 | */ | ||
| 170 | static int zynqmp_pm_get_api_version(u32 *version) | ||
| 171 | { | ||
| 172 | u32 ret_payload[PAYLOAD_ARG_CNT]; | ||
| 173 | int ret; | ||
| 174 | |||
| 175 | if (!version) | ||
| 176 | return -EINVAL; | ||
| 177 | |||
| 178 | /* Check is PM API version already verified */ | ||
| 179 | if (pm_api_version > 0) { | ||
| 180 | *version = pm_api_version; | ||
| 181 | return 0; | ||
| 182 | } | ||
| 183 | ret = zynqmp_pm_invoke_fn(PM_GET_API_VERSION, 0, 0, 0, 0, ret_payload); | ||
| 184 | *version = ret_payload[1]; | ||
| 185 | |||
| 186 | return ret; | ||
| 187 | } | ||
| 188 | |||
| 189 | /** | ||
| 190 | * zynqmp_pm_get_trustzone_version() - Get secure trustzone firmware version | ||
| 191 | * @version: Returned version value | ||
| 192 | * | ||
| 193 | * Return: Returns status, either success or error+reason | ||
| 194 | */ | ||
| 195 | static int zynqmp_pm_get_trustzone_version(u32 *version) | ||
| 196 | { | ||
| 197 | u32 ret_payload[PAYLOAD_ARG_CNT]; | ||
| 198 | int ret; | ||
| 199 | |||
| 200 | if (!version) | ||
| 201 | return -EINVAL; | ||
| 202 | |||
| 203 | /* Check is PM trustzone version already verified */ | ||
| 204 | if (pm_tz_version > 0) { | ||
| 205 | *version = pm_tz_version; | ||
| 206 | return 0; | ||
| 207 | } | ||
| 208 | ret = zynqmp_pm_invoke_fn(PM_GET_TRUSTZONE_VERSION, 0, 0, | ||
| 209 | 0, 0, ret_payload); | ||
| 210 | *version = ret_payload[1]; | ||
| 211 | |||
| 212 | return ret; | ||
| 213 | } | ||
| 214 | |||
| 215 | /** | ||
| 216 | * get_set_conduit_method() - Choose SMC or HVC based communication | ||
| 217 | * @np: Pointer to the device_node structure | ||
| 218 | * | ||
| 219 | * Use SMC or HVC-based functions to communicate with EL2/EL3. | ||
| 220 | * | ||
| 221 | * Return: Returns 0 on success or error code | ||
| 222 | */ | ||
| 223 | static int get_set_conduit_method(struct device_node *np) | ||
| 224 | { | ||
| 225 | const char *method; | ||
| 226 | |||
| 227 | if (of_property_read_string(np, "method", &method)) { | ||
| 228 | pr_warn("%s missing \"method\" property\n", __func__); | ||
| 229 | return -ENXIO; | ||
| 230 | } | ||
| 231 | |||
| 232 | if (!strcmp("hvc", method)) { | ||
| 233 | do_fw_call = do_fw_call_hvc; | ||
| 234 | } else if (!strcmp("smc", method)) { | ||
| 235 | do_fw_call = do_fw_call_smc; | ||
| 236 | } else { | ||
| 237 | pr_warn("%s Invalid \"method\" property: %s\n", | ||
| 238 | __func__, method); | ||
| 239 | return -EINVAL; | ||
| 240 | } | ||
| 241 | |||
| 242 | return 0; | ||
| 243 | } | ||
| 244 | |||
| 245 | /** | ||
| 246 | * zynqmp_pm_query_data() - Get query data from firmware | ||
| 247 | * @qdata: Variable to the zynqmp_pm_query_data structure | ||
| 248 | * @out: Returned output value | ||
| 249 | * | ||
| 250 | * Return: Returns status, either success or error+reason | ||
| 251 | */ | ||
| 252 | static int zynqmp_pm_query_data(struct zynqmp_pm_query_data qdata, u32 *out) | ||
| 253 | { | ||
| 254 | int ret; | ||
| 255 | |||
| 256 | ret = zynqmp_pm_invoke_fn(PM_QUERY_DATA, qdata.qid, qdata.arg1, | ||
| 257 | qdata.arg2, qdata.arg3, out); | ||
| 258 | |||
| 259 | /* | ||
| 260 | * For clock name query, all bytes in SMC response are clock name | ||
| 261 | * characters and return code is always success. For invalid clocks, | ||
| 262 | * clock name bytes would be zeros. | ||
| 263 | */ | ||
| 264 | return qdata.qid == PM_QID_CLOCK_GET_NAME ? 0 : ret; | ||
| 265 | } | ||
| 266 | |||
| 267 | /** | ||
| 268 | * zynqmp_pm_clock_enable() - Enable the clock for given id | ||
| 269 | * @clock_id: ID of the clock to be enabled | ||
| 270 | * | ||
| 271 | * This function is used by master to enable the clock | ||
| 272 | * including peripherals and PLL clocks. | ||
| 273 | * | ||
| 274 | * Return: Returns status, either success or error+reason | ||
| 275 | */ | ||
| 276 | static int zynqmp_pm_clock_enable(u32 clock_id) | ||
| 277 | { | ||
| 278 | return zynqmp_pm_invoke_fn(PM_CLOCK_ENABLE, clock_id, 0, 0, 0, NULL); | ||
| 279 | } | ||
| 280 | |||
| 281 | /** | ||
| 282 | * zynqmp_pm_clock_disable() - Disable the clock for given id | ||
| 283 | * @clock_id: ID of the clock to be disable | ||
| 284 | * | ||
| 285 | * This function is used by master to disable the clock | ||
| 286 | * including peripherals and PLL clocks. | ||
| 287 | * | ||
| 288 | * Return: Returns status, either success or error+reason | ||
| 289 | */ | ||
| 290 | static int zynqmp_pm_clock_disable(u32 clock_id) | ||
| 291 | { | ||
| 292 | return zynqmp_pm_invoke_fn(PM_CLOCK_DISABLE, clock_id, 0, 0, 0, NULL); | ||
| 293 | } | ||
| 294 | |||
| 295 | /** | ||
| 296 | * zynqmp_pm_clock_getstate() - Get the clock state for given id | ||
| 297 | * @clock_id: ID of the clock to be queried | ||
| 298 | * @state: 1/0 (Enabled/Disabled) | ||
| 299 | * | ||
| 300 | * This function is used by master to get the state of clock | ||
| 301 | * including peripherals and PLL clocks. | ||
| 302 | * | ||
| 303 | * Return: Returns status, either success or error+reason | ||
| 304 | */ | ||
| 305 | static int zynqmp_pm_clock_getstate(u32 clock_id, u32 *state) | ||
| 306 | { | ||
| 307 | u32 ret_payload[PAYLOAD_ARG_CNT]; | ||
| 308 | int ret; | ||
| 309 | |||
| 310 | ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETSTATE, clock_id, 0, | ||
| 311 | 0, 0, ret_payload); | ||
| 312 | *state = ret_payload[1]; | ||
| 313 | |||
| 314 | return ret; | ||
| 315 | } | ||
| 316 | |||
| 317 | /** | ||
| 318 | * zynqmp_pm_clock_setdivider() - Set the clock divider for given id | ||
| 319 | * @clock_id: ID of the clock | ||
| 320 | * @divider: divider value | ||
| 321 | * | ||
| 322 | * This function is used by master to set divider for any clock | ||
| 323 | * to achieve desired rate. | ||
| 324 | * | ||
| 325 | * Return: Returns status, either success or error+reason | ||
| 326 | */ | ||
| 327 | static int zynqmp_pm_clock_setdivider(u32 clock_id, u32 divider) | ||
| 328 | { | ||
| 329 | return zynqmp_pm_invoke_fn(PM_CLOCK_SETDIVIDER, clock_id, divider, | ||
| 330 | 0, 0, NULL); | ||
| 331 | } | ||
| 332 | |||
| 333 | /** | ||
| 334 | * zynqmp_pm_clock_getdivider() - Get the clock divider for given id | ||
| 335 | * @clock_id: ID of the clock | ||
| 336 | * @divider: divider value | ||
| 337 | * | ||
| 338 | * This function is used by master to get divider values | ||
| 339 | * for any clock. | ||
| 340 | * | ||
| 341 | * Return: Returns status, either success or error+reason | ||
| 342 | */ | ||
| 343 | static int zynqmp_pm_clock_getdivider(u32 clock_id, u32 *divider) | ||
| 344 | { | ||
| 345 | u32 ret_payload[PAYLOAD_ARG_CNT]; | ||
| 346 | int ret; | ||
| 347 | |||
| 348 | ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETDIVIDER, clock_id, 0, | ||
| 349 | 0, 0, ret_payload); | ||
| 350 | *divider = ret_payload[1]; | ||
| 351 | |||
| 352 | return ret; | ||
| 353 | } | ||
| 354 | |||
| 355 | /** | ||
| 356 | * zynqmp_pm_clock_setrate() - Set the clock rate for given id | ||
| 357 | * @clock_id: ID of the clock | ||
| 358 | * @rate: rate value in hz | ||
| 359 | * | ||
| 360 | * This function is used by master to set rate for any clock. | ||
| 361 | * | ||
| 362 | * Return: Returns status, either success or error+reason | ||
| 363 | */ | ||
| 364 | static int zynqmp_pm_clock_setrate(u32 clock_id, u64 rate) | ||
| 365 | { | ||
| 366 | return zynqmp_pm_invoke_fn(PM_CLOCK_SETRATE, clock_id, | ||
| 367 | lower_32_bits(rate), | ||
| 368 | upper_32_bits(rate), | ||
| 369 | 0, NULL); | ||
| 370 | } | ||
| 371 | |||
| 372 | /** | ||
| 373 | * zynqmp_pm_clock_getrate() - Get the clock rate for given id | ||
| 374 | * @clock_id: ID of the clock | ||
| 375 | * @rate: rate value in hz | ||
| 376 | * | ||
| 377 | * This function is used by master to get rate | ||
| 378 | * for any clock. | ||
| 379 | * | ||
| 380 | * Return: Returns status, either success or error+reason | ||
| 381 | */ | ||
| 382 | static int zynqmp_pm_clock_getrate(u32 clock_id, u64 *rate) | ||
| 383 | { | ||
| 384 | u32 ret_payload[PAYLOAD_ARG_CNT]; | ||
| 385 | int ret; | ||
| 386 | |||
| 387 | ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETRATE, clock_id, 0, | ||
| 388 | 0, 0, ret_payload); | ||
| 389 | *rate = ((u64)ret_payload[2] << 32) | ret_payload[1]; | ||
| 390 | |||
| 391 | return ret; | ||
| 392 | } | ||
| 393 | |||
| 394 | /** | ||
| 395 | * zynqmp_pm_clock_setparent() - Set the clock parent for given id | ||
| 396 | * @clock_id: ID of the clock | ||
| 397 | * @parent_id: parent id | ||
| 398 | * | ||
| 399 | * This function is used by master to set parent for any clock. | ||
| 400 | * | ||
| 401 | * Return: Returns status, either success or error+reason | ||
| 402 | */ | ||
| 403 | static int zynqmp_pm_clock_setparent(u32 clock_id, u32 parent_id) | ||
| 404 | { | ||
| 405 | return zynqmp_pm_invoke_fn(PM_CLOCK_SETPARENT, clock_id, | ||
| 406 | parent_id, 0, 0, NULL); | ||
| 407 | } | ||
| 408 | |||
| 409 | /** | ||
| 410 | * zynqmp_pm_clock_getparent() - Get the clock parent for given id | ||
| 411 | * @clock_id: ID of the clock | ||
| 412 | * @parent_id: parent id | ||
| 413 | * | ||
| 414 | * This function is used by master to get parent index | ||
| 415 | * for any clock. | ||
| 416 | * | ||
| 417 | * Return: Returns status, either success or error+reason | ||
| 418 | */ | ||
| 419 | static int zynqmp_pm_clock_getparent(u32 clock_id, u32 *parent_id) | ||
| 420 | { | ||
| 421 | u32 ret_payload[PAYLOAD_ARG_CNT]; | ||
| 422 | int ret; | ||
| 423 | |||
| 424 | ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETPARENT, clock_id, 0, | ||
| 425 | 0, 0, ret_payload); | ||
| 426 | *parent_id = ret_payload[1]; | ||
| 427 | |||
| 428 | return ret; | ||
| 429 | } | ||
| 430 | |||
| 431 | /** | ||
| 432 | * zynqmp_is_valid_ioctl() - Check whether IOCTL ID is valid or not | ||
| 433 | * @ioctl_id: IOCTL ID | ||
| 434 | * | ||
| 435 | * Return: 1 if IOCTL is valid else 0 | ||
| 436 | */ | ||
| 437 | static inline int zynqmp_is_valid_ioctl(u32 ioctl_id) | ||
| 438 | { | ||
| 439 | switch (ioctl_id) { | ||
| 440 | case IOCTL_SET_PLL_FRAC_MODE: | ||
| 441 | case IOCTL_GET_PLL_FRAC_MODE: | ||
| 442 | case IOCTL_SET_PLL_FRAC_DATA: | ||
| 443 | case IOCTL_GET_PLL_FRAC_DATA: | ||
| 444 | return 1; | ||
| 445 | default: | ||
| 446 | return 0; | ||
| 447 | } | ||
| 448 | } | ||
| 449 | |||
| 450 | /** | ||
| 451 | * zynqmp_pm_ioctl() - PM IOCTL API for device control and configs | ||
| 452 | * @node_id: Node ID of the device | ||
| 453 | * @ioctl_id: ID of the requested IOCTL | ||
| 454 | * @arg1: Argument 1 to requested IOCTL call | ||
| 455 | * @arg2: Argument 2 to requested IOCTL call | ||
| 456 | * @out: Returned output value | ||
| 457 | * | ||
| 458 | * This function calls IOCTL to firmware for device control and configuration. | ||
| 459 | * | ||
| 460 | * Return: Returns status, either success or error+reason | ||
| 461 | */ | ||
| 462 | static int zynqmp_pm_ioctl(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2, | ||
| 463 | u32 *out) | ||
| 464 | { | ||
| 465 | if (!zynqmp_is_valid_ioctl(ioctl_id)) | ||
| 466 | return -EINVAL; | ||
| 467 | |||
| 468 | return zynqmp_pm_invoke_fn(PM_IOCTL, node_id, ioctl_id, | ||
| 469 | arg1, arg2, out); | ||
| 470 | } | ||
| 471 | |||
| 472 | static const struct zynqmp_eemi_ops eemi_ops = { | ||
| 473 | .get_api_version = zynqmp_pm_get_api_version, | ||
| 474 | .query_data = zynqmp_pm_query_data, | ||
| 475 | .clock_enable = zynqmp_pm_clock_enable, | ||
| 476 | .clock_disable = zynqmp_pm_clock_disable, | ||
| 477 | .clock_getstate = zynqmp_pm_clock_getstate, | ||
| 478 | .clock_setdivider = zynqmp_pm_clock_setdivider, | ||
| 479 | .clock_getdivider = zynqmp_pm_clock_getdivider, | ||
| 480 | .clock_setrate = zynqmp_pm_clock_setrate, | ||
| 481 | .clock_getrate = zynqmp_pm_clock_getrate, | ||
| 482 | .clock_setparent = zynqmp_pm_clock_setparent, | ||
| 483 | .clock_getparent = zynqmp_pm_clock_getparent, | ||
| 484 | .ioctl = zynqmp_pm_ioctl, | ||
| 485 | }; | ||
| 486 | |||
| 487 | /** | ||
| 488 | * zynqmp_pm_get_eemi_ops - Get eemi ops functions | ||
| 489 | * | ||
| 490 | * Return: Pointer of eemi_ops structure | ||
| 491 | */ | ||
| 492 | const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void) | ||
| 493 | { | ||
| 494 | return &eemi_ops; | ||
| 495 | } | ||
| 496 | EXPORT_SYMBOL_GPL(zynqmp_pm_get_eemi_ops); | ||
| 497 | |||
| 498 | static int zynqmp_firmware_probe(struct platform_device *pdev) | ||
| 499 | { | ||
| 500 | struct device *dev = &pdev->dev; | ||
| 501 | struct device_node *np; | ||
| 502 | int ret; | ||
| 503 | |||
| 504 | np = of_find_compatible_node(NULL, NULL, "xlnx,zynqmp"); | ||
| 505 | if (!np) | ||
| 506 | return 0; | ||
| 507 | of_node_put(np); | ||
| 508 | |||
| 509 | ret = get_set_conduit_method(dev->of_node); | ||
| 510 | if (ret) | ||
| 511 | return ret; | ||
| 512 | |||
| 513 | /* Check PM API version number */ | ||
| 514 | zynqmp_pm_get_api_version(&pm_api_version); | ||
| 515 | if (pm_api_version < ZYNQMP_PM_VERSION) { | ||
| 516 | panic("%s Platform Management API version error. Expected: v%d.%d - Found: v%d.%d\n", | ||
| 517 | __func__, | ||
| 518 | ZYNQMP_PM_VERSION_MAJOR, ZYNQMP_PM_VERSION_MINOR, | ||
| 519 | pm_api_version >> 16, pm_api_version & 0xFFFF); | ||
| 520 | } | ||
| 521 | |||
| 522 | pr_info("%s Platform Management API v%d.%d\n", __func__, | ||
| 523 | pm_api_version >> 16, pm_api_version & 0xFFFF); | ||
| 524 | |||
| 525 | /* Check trustzone version number */ | ||
| 526 | ret = zynqmp_pm_get_trustzone_version(&pm_tz_version); | ||
| 527 | if (ret) | ||
| 528 | panic("Legacy trustzone found without version support\n"); | ||
| 529 | |||
| 530 | if (pm_tz_version < ZYNQMP_TZ_VERSION) | ||
| 531 | panic("%s Trustzone version error. Expected: v%d.%d - Found: v%d.%d\n", | ||
| 532 | __func__, | ||
| 533 | ZYNQMP_TZ_VERSION_MAJOR, ZYNQMP_TZ_VERSION_MINOR, | ||
| 534 | pm_tz_version >> 16, pm_tz_version & 0xFFFF); | ||
| 535 | |||
| 536 | pr_info("%s Trustzone version v%d.%d\n", __func__, | ||
| 537 | pm_tz_version >> 16, pm_tz_version & 0xFFFF); | ||
| 538 | |||
| 539 | zynqmp_pm_api_debugfs_init(); | ||
| 540 | |||
| 541 | return of_platform_populate(dev->of_node, NULL, NULL, dev); | ||
| 542 | } | ||
| 543 | |||
| 544 | static int zynqmp_firmware_remove(struct platform_device *pdev) | ||
| 545 | { | ||
| 546 | zynqmp_pm_api_debugfs_exit(); | ||
| 547 | |||
| 548 | return 0; | ||
| 549 | } | ||
| 550 | |||
| 551 | static const struct of_device_id zynqmp_firmware_of_match[] = { | ||
| 552 | {.compatible = "xlnx,zynqmp-firmware"}, | ||
| 553 | {}, | ||
| 554 | }; | ||
| 555 | MODULE_DEVICE_TABLE(of, zynqmp_firmware_of_match); | ||
| 556 | |||
| 557 | static struct platform_driver zynqmp_firmware_driver = { | ||
| 558 | .driver = { | ||
| 559 | .name = "zynqmp_firmware", | ||
| 560 | .of_match_table = zynqmp_firmware_of_match, | ||
| 561 | }, | ||
| 562 | .probe = zynqmp_firmware_probe, | ||
| 563 | .remove = zynqmp_firmware_remove, | ||
| 564 | }; | ||
| 565 | module_platform_driver(zynqmp_firmware_driver); | ||
