summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-zynqmp/firmware-zynqmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-zynqmp/firmware-zynqmp.c')
-rw-r--r--arch/arm/mach-zynqmp/firmware-zynqmp.c203
1 files changed, 198 insertions, 5 deletions
diff --git a/arch/arm/mach-zynqmp/firmware-zynqmp.c b/arch/arm/mach-zynqmp/firmware-zynqmp.c
index c23b434031..039a46e767 100644
--- a/arch/arm/mach-zynqmp/firmware-zynqmp.c
+++ b/arch/arm/mach-zynqmp/firmware-zynqmp.c
@@ -14,9 +14,17 @@
#include <common.h>
#include <init.h>
+#include <driver.h>
+#include <param.h>
#include <linux/arm-smccc.h>
-#include <mach/firmware-zynqmp.h>
+#include <mach/zynqmp/firmware-zynqmp.h>
+
+struct zynqmp_fw {
+ struct device *dev;
+ u32 ggs[4];
+ u32 pggs[4];
+};
#define ZYNQMP_TZ_VERSION(MAJOR, MINOR) ((MAJOR << 16) | MINOR)
@@ -40,6 +48,7 @@ enum pm_ret_status {
enum pm_api_id {
PM_GET_API_VERSION = 1,
+ PM_MMIO_WRITE = 19,
PM_FPGA_LOAD = 22,
PM_FPGA_GET_STATUS,
PM_IOCTL = 34,
@@ -504,6 +513,130 @@ static int zynqmp_pm_ioctl(u32 node_id, u32 ioctl_id, u32 arg1, u32 arg2,
}
/**
+ * zynqmp_pm_set_sd_tapdelay() - Set tap delay for the SD device
+ *
+ * @node_id: Node ID of the device
+ * @type: Type of tap delay to set (input/output)
+ * @value: Value to set fot the tap delay
+ *
+ * This function sets input/output tap delay for the SD device.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+int zynqmp_pm_set_sd_tapdelay(u32 node_id, u32 type, u32 value)
+{
+ u32 reg = (type == PM_TAPDELAY_INPUT) ? SD_ITAPDLY : SD_OTAPDLYSEL;
+ u32 mask = (node_id == NODE_SD_0) ? GENMASK(15, 0) : GENMASK(31, 16);
+
+ if (value) {
+ return zynqmp_pm_invoke_fn(PM_IOCTL, node_id,
+ IOCTL_SET_SD_TAPDELAY,
+ type, value, NULL);
+ }
+
+ /*
+ * Work around completely misdesigned firmware API on Xilinx ZynqMP.
+ * The IOCTL_SET_SD_TAPDELAY firmware call allows the caller to only
+ * ever set IOU_SLCR SD_ITAPDLY Register SD0_ITAPDLYENA/SD1_ITAPDLYENA
+ * bits, but there is no matching call to clear those bits. If those
+ * bits are not cleared, SDMMC tuning may fail.
+ *
+ * Luckily, there are PM_MMIO_READ/PM_MMIO_WRITE calls which seem to
+ * allow complete unrestricted access to all address space, including
+ * IOU_SLCR SD_ITAPDLY Register and all the other registers, access
+ * to which was supposed to be protected by the current firmware API.
+ *
+ * Use PM_MMIO_READ/PM_MMIO_WRITE to re-implement the missing counter
+ * part of IOCTL_SET_SD_TAPDELAY which clears SDx_ITAPDLYENA bits.
+ */
+ return zynqmp_pm_invoke_fn(PM_MMIO_WRITE, reg, mask, 0, 0, NULL);
+}
+EXPORT_SYMBOL_GPL(zynqmp_pm_set_sd_tapdelay);
+
+/**
+ * zynqmp_pm_sd_dll_reset() - Reset DLL logic
+ *
+ * @node_id: Node ID of the device
+ * @type: Reset type
+ *
+ * This function resets DLL logic for the SD device.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+int zynqmp_pm_sd_dll_reset(u32 node_id, u32 type)
+{
+ return zynqmp_pm_invoke_fn(PM_IOCTL, node_id, IOCTL_SD_DLL_RESET,
+ type, 0, NULL);
+}
+EXPORT_SYMBOL_GPL(zynqmp_pm_sd_dll_reset);
+
+/*
+ * zynqmp_pm_write_ggs() - PM API for writing global general storage (ggs)
+ * @index: GGS register index
+ * @value: Register value to be written
+ *
+ * This function writes value to GGS register.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+int zynqmp_pm_write_ggs(u32 index, u32 value)
+{
+ return zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_WRITE_GGS,
+ index, value, NULL);
+}
+EXPORT_SYMBOL_GPL(zynqmp_pm_write_ggs);
+
+/**
+ * zynqmp_pm_read_ggs() - PM API for reading global general storage (ggs)
+ * @index: GGS register index
+ * @value: Register value to be written
+ *
+ * This function returns GGS register value.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+int zynqmp_pm_read_ggs(u32 index, u32 *value)
+{
+ return zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_READ_GGS,
+ index, 0, value);
+}
+EXPORT_SYMBOL_GPL(zynqmp_pm_read_ggs);
+
+/**
+ * zynqmp_pm_write_pggs() - PM API for writing persistent global general
+ * storage (pggs)
+ * @index: PGGS register index
+ * @value: Register value to be written
+ *
+ * This function writes value to PGGS register.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+int zynqmp_pm_write_pggs(u32 index, u32 value)
+{
+ return zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_WRITE_PGGS, index, value,
+ NULL);
+}
+EXPORT_SYMBOL_GPL(zynqmp_pm_write_pggs);
+
+/**
+ * zynqmp_pm_read_pggs() - PM API for reading persistent global general
+ * storage (pggs)
+ * @index: PGGS register index
+ * @value: Register value to be written
+ *
+ * This function returns PGGS register value.
+ *
+ * Return: Returns status, either success or error+reason
+ */
+int zynqmp_pm_read_pggs(u32 index, u32 *value)
+{
+ return zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_READ_PGGS, index, 0,
+ value);
+}
+EXPORT_SYMBOL_GPL(zynqmp_pm_read_pggs);
+
+/**
* zynqmp_pm_fpga_load - Perform the fpga load
* @address: Address to write to
* @size: pl bitstream size
@@ -576,12 +709,59 @@ const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void)
}
EXPORT_SYMBOL_GPL(zynqmp_pm_get_eemi_ops);
+static bool parse_reg(const char *reg, unsigned *idx)
+{
+ bool pggs = reg[0] == 'p';
+ kstrtouint(reg + pggs + sizeof("ggs") - 1, 10, idx);
+ return pggs;
+}
-static int zynqmp_firmware_probe(struct device_d *dev)
+static int ggs_set(struct param_d *p, void *_val)
+{
+ u32 *val = _val;
+ unsigned idx;
+
+ if (parse_reg(p->name, &idx))
+ return zynqmp_pm_write_pggs(idx, *val);
+ else
+ return zynqmp_pm_write_ggs(idx, *val);
+}
+static int ggs_get(struct param_d *p, void *_val)
{
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ u32 *val = _val;
+ unsigned idx;
+ int ret;
+
+ if (parse_reg(p->name, &idx))
+ ret = zynqmp_pm_read_pggs(idx, ret_payload);
+ else
+ ret = zynqmp_pm_read_ggs(idx, ret_payload);
+
+ if (ret)
+ return ret;
+
+ *val = ret_payload[1];
+
+ return 0;
+}
+
+static inline void dev_add_param_ggs(struct zynqmp_fw *fw, const char *str, u32 *value)
+{
+ dev_add_param_uint32(fw->dev, str, ggs_set, ggs_get, value, "0x%x", value);
+}
+
+static int zynqmp_firmware_probe(struct device *dev)
+{
+
+ struct zynqmp_fw *fw;
int ret;
- ret = get_set_conduit_method(dev->device_node);
+ fw = xzalloc(sizeof(*fw));
+
+ dev_add_alias(dev, "zynqmp_fw");
+
+ ret = get_set_conduit_method(dev->of_node);
if (ret)
goto out;
@@ -619,7 +799,19 @@ static int zynqmp_firmware_probe(struct device_d *dev)
dev_dbg(dev, "Trustzone version v%d.%d\n",
pm_tz_version >> 16, pm_tz_version & 0xFFFF);
- of_platform_populate(dev->device_node, NULL, dev);
+ of_platform_populate(dev->of_node, NULL, dev);
+
+ fw->dev = dev;
+
+ dev_add_param_ggs(fw, "ggs0", &fw->ggs[0]);
+ dev_add_param_ggs(fw, "ggs1", &fw->ggs[1]);
+ dev_add_param_ggs(fw, "ggs2", &fw->ggs[2]);
+ dev_add_param_ggs(fw, "ggs3", &fw->ggs[3]);
+
+ dev_add_param_ggs(fw, "pggs0", &fw->pggs[0]);
+ dev_add_param_ggs(fw, "pggs1", &fw->pggs[1]);
+ dev_add_param_ggs(fw, "pggs2", &fw->pggs[2]);
+ dev_add_param_ggs(fw, "pggs3", &fw->pggs[3]);
out:
if (ret)
do_fw_call = do_fw_call_fail;
@@ -630,8 +822,9 @@ static struct of_device_id zynqmp_firmware_id_table[] = {
{ .compatible = "xlnx,zynqmp-firmware", },
{}
};
+MODULE_DEVICE_TABLE(of, zynqmp_firmware_id_table);
-static struct driver_d zynqmp_firmware_driver = {
+static struct driver zynqmp_firmware_driver = {
.name = "zynqmp_firmware",
.probe = zynqmp_firmware_probe,
.of_compatible = DRV_OF_COMPAT(zynqmp_firmware_id_table),