diff options
Diffstat (limited to 'arch/arm/mach-zynqmp/firmware-zynqmp.c')
-rw-r--r-- | arch/arm/mach-zynqmp/firmware-zynqmp.c | 203 |
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), |