From a14b820316e84310b1bad3701a8d4c9159377633 Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Thu, 18 Jul 2019 18:32:36 +0530 Subject: soc: qcom: llcc cleanup to get rid of sdm845 specific driver file A single file should suffice the need to program the llcc for various platforms. Get rid of sdm845 specific driver file to make way for a more generic driver. Signed-off-by: Vivek Gautam Signed-off-by: Bjorn Andersson --- include/linux/soc/qcom/llcc-qcom.h | 57 ++++++++++++++------------------------ 1 file changed, 21 insertions(+), 36 deletions(-) (limited to 'include') diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h index eb71a50b8afc..d5cad6f7953c 100644 --- a/include/linux/soc/qcom/llcc-qcom.h +++ b/include/linux/soc/qcom/llcc-qcom.h @@ -39,18 +39,27 @@ struct llcc_slice_desc { /** * llcc_slice_config - Data associated with the llcc slice - * @usecase_id: usecase id for which the llcc slice is used - * @slice_id: llcc slice id assigned to each slice - * @max_cap: maximum capacity of the llcc slice - * @priority: priority of the llcc slice - * @fixed_size: whether the llcc slice can grow beyond its size - * @bonus_ways: bonus ways associated with llcc slice - * @res_ways: reserved ways associated with llcc slice - * @cache_mode: mode of the llcc slice - * @probe_target_ways: Probe only reserved and bonus ways on a cache miss - * @dis_cap_alloc: Disable capacity based allocation - * @retain_on_pc: Retain through power collapse - * @activate_on_init: activate the slice on init + * @usecase_id: Unique id for the client's use case + * @slice_id: llcc slice id for each client + * @max_cap: The maximum capacity of the cache slice provided in KB + * @priority: Priority of the client used to select victim line for replacement + * @fixed_size: Boolean indicating if the slice has a fixed capacity + * @bonus_ways: Bonus ways are additional ways to be used for any slice, + * if client ends up using more than reserved cache ways. Bonus + * ways are allocated only if they are not reserved for some + * other client. + * @res_ways: Reserved ways for the cache slice, the reserved ways cannot + * be used by any other client than the one its assigned to. + * @cache_mode: Each slice operates as a cache, this controls the mode of the + * slice: normal or TCM(Tightly Coupled Memory) + * @probe_target_ways: Determines what ways to probe for access hit. When + * configured to 1 only bonus and reserved ways are probed. + * When configured to 0 all ways in llcc are probed. + * @dis_cap_alloc: Disable capacity based allocation for a client + * @retain_on_pc: If this bit is set and client has maintained active vote + * then the ways assigned to this client are not flushed on power + * collapse. + * @activate_on_init: Activate the slice immediately after it is programmed */ struct llcc_slice_config { u32 usecase_id; @@ -154,20 +163,6 @@ int llcc_slice_activate(struct llcc_slice_desc *desc); */ int llcc_slice_deactivate(struct llcc_slice_desc *desc); -/** - * qcom_llcc_probe - program the sct table - * @pdev: platform device pointer - * @table: soc sct table - * @sz: Size of the config table - */ -int qcom_llcc_probe(struct platform_device *pdev, - const struct llcc_slice_config *table, u32 sz); - -/** - * qcom_llcc_remove - remove the sct table - * @pdev: Platform device pointer - */ -int qcom_llcc_remove(struct platform_device *pdev); #else static inline struct llcc_slice_desc *llcc_slice_getd(u32 uid) { @@ -197,16 +192,6 @@ static inline int llcc_slice_deactivate(struct llcc_slice_desc *desc) { return -EINVAL; } -static inline int qcom_llcc_probe(struct platform_device *pdev, - const struct llcc_slice_config *table, u32 sz) -{ - return -ENODEV; -} - -static inline int qcom_llcc_remove(struct platform_device *pdev) -{ - return -ENODEV; -} #endif #endif -- cgit v1.2.3-59-g8ed1b From 99356b03b431f9589bbaec2bc5bacceccb3dd99a Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Thu, 18 Jul 2019 18:32:38 +0530 Subject: soc: qcom: Make llcc-qcom a generic driver This makes way for adding future llcc versions. Also pull out the llcc-qcom specific definitions from includes. Includes path now contains the only definitions that are to be exposed to other subsystems. Signed-off-by: Vivek Gautam Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/llcc-qcom.c | 137 +++++++++++++++++++++++++++++++------ include/linux/soc/qcom/llcc-qcom.h | 89 ------------------------ 2 files changed, 116 insertions(+), 110 deletions(-) (limited to 'include') diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index 574bb5bf20bc..98563ef0ac6b 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -47,6 +47,100 @@ #define BANK_OFFSET_STRIDE 0x80000 +/** + * llcc_slice_config - Data associated with the llcc slice + * @usecase_id: Unique id for the client's use case + * @slice_id: llcc slice id for each client + * @max_cap: The maximum capacity of the cache slice provided in KB + * @priority: Priority of the client used to select victim line for replacement + * @fixed_size: Boolean indicating if the slice has a fixed capacity + * @bonus_ways: Bonus ways are additional ways to be used for any slice, + * if client ends up using more than reserved cache ways. Bonus + * ways are allocated only if they are not reserved for some + * other client. + * @res_ways: Reserved ways for the cache slice, the reserved ways cannot + * be used by any other client than the one its assigned to. + * @cache_mode: Each slice operates as a cache, this controls the mode of the + * slice: normal or TCM(Tightly Coupled Memory) + * @probe_target_ways: Determines what ways to probe for access hit. When + * configured to 1 only bonus and reserved ways are probed. + * When configured to 0 all ways in llcc are probed. + * @dis_cap_alloc: Disable capacity based allocation for a client + * @retain_on_pc: If this bit is set and client has maintained active vote + * then the ways assigned to this client are not flushed on power + * collapse. + * @activate_on_init: Activate the slice immediately after it is programmed + */ +struct llcc_slice_config { + u32 usecase_id; + u32 slice_id; + u32 max_cap; + u32 priority; + bool fixed_size; + u32 bonus_ways; + u32 res_ways; + u32 cache_mode; + u32 probe_target_ways; + bool dis_cap_alloc; + bool retain_on_pc; + bool activate_on_init; +}; + +/** + * llcc_drv_data - Data associated with the llcc driver + * @regmap: regmap associated with the llcc device + * @bcast_regmap: regmap associated with llcc broadcast offset + * @cfg: pointer to the data structure for slice configuration + * @lock: mutex associated with each slice + * @cfg_size: size of the config data table + * @max_slices: max slices as read from device tree + * @num_banks: Number of llcc banks + * @bitmap: Bit map to track the active slice ids + * @offsets: Pointer to the bank offsets array + * @ecc_irq: interrupt for llcc cache error detection and reporting + */ +struct llcc_drv_data { + struct regmap *regmap; + struct regmap *bcast_regmap; + const struct llcc_slice_config *cfg; + struct mutex lock; + u32 cfg_size; + u32 max_slices; + u32 num_banks; + unsigned long *bitmap; + u32 *offsets; + int ecc_irq; +}; + +/** + * llcc_edac_reg_data - llcc edac registers data for each error type + * @name: Name of the error + * @synd_reg: Syndrome register address + * @count_status_reg: Status register address to read the error count + * @ways_status_reg: Status register address to read the error ways + * @reg_cnt: Number of registers + * @count_mask: Mask value to get the error count + * @ways_mask: Mask value to get the error ways + * @count_shift: Shift value to get the error count + * @ways_shift: Shift value to get the error ways + */ +struct llcc_edac_reg_data { + char *name; + u64 synd_reg; + u64 count_status_reg; + u64 ways_status_reg; + u32 reg_cnt; + u32 count_mask; + u32 ways_mask; + u8 count_shift; + u8 ways_shift; +}; + +struct qcom_llcc_config { + const struct llcc_slice_config *sct_data; + int size; +}; + static struct llcc_slice_config sdm845_data[] = { { LLCC_CPUSS, 1, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 1 }, { LLCC_VIDSC0, 2, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0 }, @@ -68,6 +162,11 @@ static struct llcc_slice_config sdm845_data[] = { { LLCC_AUDHW, 22, 1024, 1, 1, 0xffc, 0x2, 0, 0, 1, 1, 0 }, }; +static const struct qcom_llcc_config sdm845_cfg = { + .sct_data = sdm845_data, + .size = ARRAY_SIZE(sdm845_data), +}; + static struct llcc_drv_data *drv_data = (void *) -EPROBE_DEFER; static const struct regmap_config llcc_regmap_config = { @@ -347,13 +446,15 @@ static struct regmap *qcom_llcc_init_mmio(struct platform_device *pdev, return devm_regmap_init_mmio(&pdev->dev, base, &llcc_regmap_config); } -static int qcom_llcc_probe(struct platform_device *pdev, - const struct llcc_slice_config *llcc_cfg, u32 sz) +static int qcom_llcc_probe(struct platform_device *pdev) { u32 num_banks; struct device *dev = &pdev->dev; int ret, i; struct platform_device *llcc_edac; + const struct qcom_llcc_config *cfg; + const struct llcc_slice_config *llcc_cfg; + u32 sz; drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL); if (!drv_data) { @@ -383,6 +484,10 @@ static int qcom_llcc_probe(struct platform_device *pdev, num_banks >>= LLCC_LB_CNT_SHIFT; drv_data->num_banks = num_banks; + cfg = of_device_get_match_data(&pdev->dev); + llcc_cfg = cfg->sct_data; + sz = cfg->size; + for (i = 0; i < sz; i++) if (llcc_cfg[i].slice_id > drv_data->max_slices) drv_data->max_slices = llcc_cfg[i].slice_id; @@ -429,30 +534,20 @@ err: return ret; } -static int sdm845_qcom_llcc_remove(struct platform_device *pdev) -{ - return qcom_llcc_remove(pdev); -} - -static int sdm845_qcom_llcc_probe(struct platform_device *pdev) -{ - return qcom_llcc_probe(pdev, sdm845_data, ARRAY_SIZE(sdm845_data)); -} - -static const struct of_device_id sdm845_qcom_llcc_of_match[] = { - { .compatible = "qcom,sdm845-llcc", }, +static const struct of_device_id qcom_llcc_of_match[] = { + { .compatible = "qcom,sdm845-llcc", .data = &sdm845_cfg }, { } }; -static struct platform_driver sdm845_qcom_llcc_driver = { +static struct platform_driver qcom_llcc_driver = { .driver = { - .name = "sdm845-llcc", - .of_match_table = sdm845_qcom_llcc_of_match, + .name = "qcom-llcc", + .of_match_table = qcom_llcc_of_match, }, - .probe = sdm845_qcom_llcc_probe, - .remove = sdm845_qcom_llcc_remove, + .probe = qcom_llcc_probe, + .remove = qcom_llcc_remove, }; -module_platform_driver(sdm845_qcom_llcc_driver); +module_platform_driver(qcom_llcc_driver); -MODULE_DESCRIPTION("QCOM sdm845 LLCC driver"); +MODULE_DESCRIPTION("Qualcomm Last Level Cache Controller"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h index d5cad6f7953c..c0acdb28fde8 100644 --- a/include/linux/soc/qcom/llcc-qcom.h +++ b/include/linux/soc/qcom/llcc-qcom.h @@ -37,95 +37,6 @@ struct llcc_slice_desc { size_t slice_size; }; -/** - * llcc_slice_config - Data associated with the llcc slice - * @usecase_id: Unique id for the client's use case - * @slice_id: llcc slice id for each client - * @max_cap: The maximum capacity of the cache slice provided in KB - * @priority: Priority of the client used to select victim line for replacement - * @fixed_size: Boolean indicating if the slice has a fixed capacity - * @bonus_ways: Bonus ways are additional ways to be used for any slice, - * if client ends up using more than reserved cache ways. Bonus - * ways are allocated only if they are not reserved for some - * other client. - * @res_ways: Reserved ways for the cache slice, the reserved ways cannot - * be used by any other client than the one its assigned to. - * @cache_mode: Each slice operates as a cache, this controls the mode of the - * slice: normal or TCM(Tightly Coupled Memory) - * @probe_target_ways: Determines what ways to probe for access hit. When - * configured to 1 only bonus and reserved ways are probed. - * When configured to 0 all ways in llcc are probed. - * @dis_cap_alloc: Disable capacity based allocation for a client - * @retain_on_pc: If this bit is set and client has maintained active vote - * then the ways assigned to this client are not flushed on power - * collapse. - * @activate_on_init: Activate the slice immediately after it is programmed - */ -struct llcc_slice_config { - u32 usecase_id; - u32 slice_id; - u32 max_cap; - u32 priority; - bool fixed_size; - u32 bonus_ways; - u32 res_ways; - u32 cache_mode; - u32 probe_target_ways; - bool dis_cap_alloc; - bool retain_on_pc; - bool activate_on_init; -}; - -/** - * llcc_drv_data - Data associated with the llcc driver - * @regmap: regmap associated with the llcc device - * @bcast_regmap: regmap associated with llcc broadcast offset - * @cfg: pointer to the data structure for slice configuration - * @lock: mutex associated with each slice - * @cfg_size: size of the config data table - * @max_slices: max slices as read from device tree - * @num_banks: Number of llcc banks - * @bitmap: Bit map to track the active slice ids - * @offsets: Pointer to the bank offsets array - * @ecc_irq: interrupt for llcc cache error detection and reporting - */ -struct llcc_drv_data { - struct regmap *regmap; - struct regmap *bcast_regmap; - const struct llcc_slice_config *cfg; - struct mutex lock; - u32 cfg_size; - u32 max_slices; - u32 num_banks; - unsigned long *bitmap; - u32 *offsets; - int ecc_irq; -}; - -/** - * llcc_edac_reg_data - llcc edac registers data for each error type - * @name: Name of the error - * @synd_reg: Syndrome register address - * @count_status_reg: Status register address to read the error count - * @ways_status_reg: Status register address to read the error ways - * @reg_cnt: Number of registers - * @count_mask: Mask value to get the error count - * @ways_mask: Mask value to get the error ways - * @count_shift: Shift value to get the error count - * @ways_shift: Shift value to get the error ways - */ -struct llcc_edac_reg_data { - char *name; - u64 synd_reg; - u64 count_status_reg; - u64 ways_status_reg; - u32 reg_cnt; - u32 count_mask; - u32 ways_mask; - u8 count_shift; - u8 ways_shift; -}; - #if IS_ENABLED(CONFIG_QCOM_LLCC) /** * llcc_slice_getd - get llcc slice descriptor -- cgit v1.2.3-59-g8ed1b From 8cde3c2153e8f57be884c0e73f18bc4de150e870 Mon Sep 17 00:00:00 2001 From: Carlo Caione Date: Wed, 31 Jul 2019 09:23:39 +0100 Subject: firmware: meson_sm: Rework driver as a proper platform driver The secure monitor driver is currently a frankenstein driver which is registered as a platform driver but its functionality goes through a global struct accessed by the consumer drivers using exported helper functions. Try to tidy up the driver moving the firmware struct into the driver data and make the consumer drivers referencing the secure-monitor using a new property in the DT. Currently only the nvmem driver is using this API so we can fix it in the same commit. Reviewed-by: Jerome Brunet Signed-off-by: Carlo Caione Signed-off-by: Kevin Hilman --- drivers/firmware/meson/meson_sm.c | 94 ++++++++++++++++++++++----------- drivers/nvmem/meson-efuse.c | 24 +++++++-- include/linux/firmware/meson/meson_sm.h | 15 +++--- 3 files changed, 94 insertions(+), 39 deletions(-) (limited to 'include') diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c index 772ca6726e7b..2e36a2aa274c 100644 --- a/drivers/firmware/meson/meson_sm.c +++ b/drivers/firmware/meson/meson_sm.c @@ -54,8 +54,6 @@ struct meson_sm_firmware { void __iomem *sm_shmem_out_base; }; -static struct meson_sm_firmware fw; - static u32 meson_sm_get_cmd(const struct meson_sm_chip *chip, unsigned int cmd_index) { @@ -90,6 +88,7 @@ static void __iomem *meson_sm_map_shmem(u32 cmd_shmem, unsigned int size) /** * meson_sm_call - generic SMC32 call to the secure-monitor * + * @fw: Pointer to secure-monitor firmware * @cmd_index: Index of the SMC32 function ID * @ret: Returned value * @arg0: SMC32 Argument 0 @@ -100,15 +99,15 @@ static void __iomem *meson_sm_map_shmem(u32 cmd_shmem, unsigned int size) * * Return: 0 on success, a negative value on error */ -int meson_sm_call(unsigned int cmd_index, u32 *ret, u32 arg0, - u32 arg1, u32 arg2, u32 arg3, u32 arg4) +int meson_sm_call(struct meson_sm_firmware *fw, unsigned int cmd_index, + u32 *ret, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) { u32 cmd, lret; - if (!fw.chip) + if (!fw->chip) return -ENOENT; - cmd = meson_sm_get_cmd(fw.chip, cmd_index); + cmd = meson_sm_get_cmd(fw->chip, cmd_index); if (!cmd) return -EINVAL; @@ -124,6 +123,7 @@ EXPORT_SYMBOL(meson_sm_call); /** * meson_sm_call_read - retrieve data from secure-monitor * + * @fw: Pointer to secure-monitor firmware * @buffer: Buffer to store the retrieved data * @bsize: Size of the buffer * @cmd_index: Index of the SMC32 function ID @@ -137,22 +137,23 @@ EXPORT_SYMBOL(meson_sm_call); * When 0 is returned there is no guarantee about the amount of * data read and bsize bytes are copied in buffer. */ -int meson_sm_call_read(void *buffer, unsigned int bsize, unsigned int cmd_index, - u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) +int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, + unsigned int bsize, unsigned int cmd_index, u32 arg0, + u32 arg1, u32 arg2, u32 arg3, u32 arg4) { u32 size; int ret; - if (!fw.chip) + if (!fw->chip) return -ENOENT; - if (!fw.chip->cmd_shmem_out_base) + if (!fw->chip->cmd_shmem_out_base) return -EINVAL; - if (bsize > fw.chip->shmem_size) + if (bsize > fw->chip->shmem_size) return -EINVAL; - if (meson_sm_call(cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0) + if (meson_sm_call(fw, cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0) return -EINVAL; if (size > bsize) @@ -164,7 +165,7 @@ int meson_sm_call_read(void *buffer, unsigned int bsize, unsigned int cmd_index, size = bsize; if (buffer) - memcpy(buffer, fw.sm_shmem_out_base, size); + memcpy(buffer, fw->sm_shmem_out_base, size); return ret; } @@ -173,6 +174,7 @@ EXPORT_SYMBOL(meson_sm_call_read); /** * meson_sm_call_write - send data to secure-monitor * + * @fw: Pointer to secure-monitor firmware * @buffer: Buffer containing data to send * @size: Size of the data to send * @cmd_index: Index of the SMC32 function ID @@ -184,23 +186,24 @@ EXPORT_SYMBOL(meson_sm_call_read); * * Return: size of sent data on success, a negative value on error */ -int meson_sm_call_write(void *buffer, unsigned int size, unsigned int cmd_index, - u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) +int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer, + unsigned int size, unsigned int cmd_index, u32 arg0, + u32 arg1, u32 arg2, u32 arg3, u32 arg4) { u32 written; - if (!fw.chip) + if (!fw->chip) return -ENOENT; - if (size > fw.chip->shmem_size) + if (size > fw->chip->shmem_size) return -EINVAL; - if (!fw.chip->cmd_shmem_in_base) + if (!fw->chip->cmd_shmem_in_base) return -EINVAL; - memcpy(fw.sm_shmem_in_base, buffer, size); + memcpy(fw->sm_shmem_in_base, buffer, size); - if (meson_sm_call(cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0) + if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0) return -EINVAL; if (!written) @@ -210,6 +213,24 @@ int meson_sm_call_write(void *buffer, unsigned int size, unsigned int cmd_index, } EXPORT_SYMBOL(meson_sm_call_write); +/** + * meson_sm_get - get pointer to meson_sm_firmware structure. + * + * @sm_node: Pointer to the secure-monitor Device Tree node. + * + * Return: NULL is the secure-monitor device is not ready. + */ +struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node) +{ + struct platform_device *pdev = of_find_device_by_node(sm_node); + + if (!pdev) + return NULL; + + return platform_get_drvdata(pdev); +} +EXPORT_SYMBOL_GPL(meson_sm_get); + #define SM_CHIP_ID_LENGTH 119 #define SM_CHIP_ID_OFFSET 4 #define SM_CHIP_ID_SIZE 12 @@ -217,14 +238,18 @@ EXPORT_SYMBOL(meson_sm_call_write); static ssize_t serial_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct platform_device *pdev = to_platform_device(dev); + struct meson_sm_firmware *fw; uint8_t *id_buf; int ret; + fw = platform_get_drvdata(pdev); + id_buf = kmalloc(SM_CHIP_ID_LENGTH, GFP_KERNEL); if (!id_buf) return -ENOMEM; - ret = meson_sm_call_read(id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID, + ret = meson_sm_call_read(fw, id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID, 0, 0, 0, 0, 0); if (ret < 0) { kfree(id_buf); @@ -268,25 +293,34 @@ static const struct of_device_id meson_sm_ids[] = { static int __init meson_sm_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; const struct meson_sm_chip *chip; + struct meson_sm_firmware *fw; + + fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL); + if (!fw) + return -ENOMEM; - chip = of_match_device(meson_sm_ids, &pdev->dev)->data; + chip = of_match_device(meson_sm_ids, dev)->data; if (chip->cmd_shmem_in_base) { - fw.sm_shmem_in_base = meson_sm_map_shmem(chip->cmd_shmem_in_base, - chip->shmem_size); - if (WARN_ON(!fw.sm_shmem_in_base)) + fw->sm_shmem_in_base = meson_sm_map_shmem(chip->cmd_shmem_in_base, + chip->shmem_size); + if (WARN_ON(!fw->sm_shmem_in_base)) goto out; } if (chip->cmd_shmem_out_base) { - fw.sm_shmem_out_base = meson_sm_map_shmem(chip->cmd_shmem_out_base, - chip->shmem_size); - if (WARN_ON(!fw.sm_shmem_out_base)) + fw->sm_shmem_out_base = meson_sm_map_shmem(chip->cmd_shmem_out_base, + chip->shmem_size); + if (WARN_ON(!fw->sm_shmem_out_base)) goto out_in_base; } - fw.chip = chip; + fw->chip = chip; + + platform_set_drvdata(pdev, fw); + pr_info("secure-monitor enabled\n"); if (sysfs_create_group(&pdev->dev.kobj, &meson_sm_sysfs_attr_group)) @@ -295,7 +329,7 @@ static int __init meson_sm_probe(struct platform_device *pdev) return 0; out_in_base: - iounmap(fw.sm_shmem_in_base); + iounmap(fw->sm_shmem_in_base); out: return -EINVAL; } diff --git a/drivers/nvmem/meson-efuse.c b/drivers/nvmem/meson-efuse.c index 39bd76306033..d6b533497ce1 100644 --- a/drivers/nvmem/meson-efuse.c +++ b/drivers/nvmem/meson-efuse.c @@ -17,14 +17,18 @@ static int meson_efuse_read(void *context, unsigned int offset, void *val, size_t bytes) { - return meson_sm_call_read((u8 *)val, bytes, SM_EFUSE_READ, offset, + struct meson_sm_firmware *fw = context; + + return meson_sm_call_read(fw, (u8 *)val, bytes, SM_EFUSE_READ, offset, bytes, 0, 0, 0); } static int meson_efuse_write(void *context, unsigned int offset, void *val, size_t bytes) { - return meson_sm_call_write((u8 *)val, bytes, SM_EFUSE_WRITE, offset, + struct meson_sm_firmware *fw = context; + + return meson_sm_call_write(fw, (u8 *)val, bytes, SM_EFUSE_WRITE, offset, bytes, 0, 0, 0); } @@ -37,12 +41,25 @@ MODULE_DEVICE_TABLE(of, meson_efuse_match); static int meson_efuse_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct meson_sm_firmware *fw; + struct device_node *sm_np; struct nvmem_device *nvmem; struct nvmem_config *econfig; struct clk *clk; unsigned int size; int ret; + sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0); + if (!sm_np) { + dev_err(&pdev->dev, "no secure-monitor node\n"); + return -ENODEV; + } + + fw = meson_sm_get(sm_np); + of_node_put(sm_np); + if (!fw) + return -EPROBE_DEFER; + clk = devm_clk_get(dev, NULL); if (IS_ERR(clk)) { ret = PTR_ERR(clk); @@ -65,7 +82,7 @@ static int meson_efuse_probe(struct platform_device *pdev) return ret; } - if (meson_sm_call(SM_EFUSE_USER_MAX, &size, 0, 0, 0, 0, 0) < 0) { + if (meson_sm_call(fw, SM_EFUSE_USER_MAX, &size, 0, 0, 0, 0, 0) < 0) { dev_err(dev, "failed to get max user"); return -EINVAL; } @@ -81,6 +98,7 @@ static int meson_efuse_probe(struct platform_device *pdev) econfig->reg_read = meson_efuse_read; econfig->reg_write = meson_efuse_write; econfig->size = size; + econfig->priv = fw; nvmem = devm_nvmem_register(&pdev->dev, econfig); diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h index 7613bf7c9442..6669e2a1d5fd 100644 --- a/include/linux/firmware/meson/meson_sm.h +++ b/include/linux/firmware/meson/meson_sm.h @@ -16,11 +16,14 @@ enum { struct meson_sm_firmware; -int meson_sm_call(unsigned int cmd_index, u32 *ret, u32 arg0, u32 arg1, - u32 arg2, u32 arg3, u32 arg4); -int meson_sm_call_write(void *buffer, unsigned int b_size, unsigned int cmd_index, - u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); -int meson_sm_call_read(void *buffer, unsigned int bsize, unsigned int cmd_index, - u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); +int meson_sm_call(struct meson_sm_firmware *fw, unsigned int cmd_index, + u32 *ret, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); +int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer, + unsigned int b_size, unsigned int cmd_index, u32 arg0, + u32 arg1, u32 arg2, u32 arg3, u32 arg4); +int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, + unsigned int bsize, unsigned int cmd_index, u32 arg0, + u32 arg1, u32 arg2, u32 arg3, u32 arg4); +struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node); #endif /* _MESON_SM_FW_H_ */ -- cgit v1.2.3-59-g8ed1b From c2016cc612db7b88483073d6ff51edd200bc6ddc Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Thu, 5 Sep 2019 15:50:39 +0200 Subject: reset: dt-bindings: meson: update arb bindings for sm1 SM1 SoC family adds two new audio FIFOs with the related arb reset lines Reviewed-by: Rob Herring Signed-off-by: Jerome Brunet Signed-off-by: Philipp Zabel --- .../devicetree/bindings/reset/amlogic,meson-axg-audio-arb.txt | 3 ++- include/dt-bindings/reset/amlogic,meson-axg-audio-arb.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.txt b/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.txt index 26e542eb96df..43e580ef64ba 100644 --- a/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.txt +++ b/Documentation/devicetree/bindings/reset/amlogic,meson-axg-audio-arb.txt @@ -4,7 +4,8 @@ The Amlogic Audio ARB is a simple device which enables or disables the access of Audio FIFOs to DDR on AXG based SoC. Required properties: -- compatible: 'amlogic,meson-axg-audio-arb' +- compatible: 'amlogic,meson-axg-audio-arb' or + 'amlogic,meson-sm1-audio-arb' - reg: physical base address of the controller and length of memory mapped region. - clocks: phandle to the fifo peripheral clock provided by the audio diff --git a/include/dt-bindings/reset/amlogic,meson-axg-audio-arb.h b/include/dt-bindings/reset/amlogic,meson-axg-audio-arb.h index 05c36367875c..1ef807856cb8 100644 --- a/include/dt-bindings/reset/amlogic,meson-axg-audio-arb.h +++ b/include/dt-bindings/reset/amlogic,meson-axg-audio-arb.h @@ -13,5 +13,7 @@ #define AXG_ARB_FRDDR_A 3 #define AXG_ARB_FRDDR_B 4 #define AXG_ARB_FRDDR_C 5 +#define AXG_ARB_TODDR_D 6 +#define AXG_ARB_FRDDR_D 7 #endif /* _DT_BINDINGS_AMLOGIC_MESON_AXG_AUDIO_ARB_H */ -- cgit v1.2.3-59-g8ed1b From 4bc6aadbcc0e2a852e584bf5410fe90ea5a5f86a Mon Sep 17 00:00:00 2001 From: "Angelo G. Del Regno" Date: Sat, 21 Sep 2019 12:12:06 +0200 Subject: dt-bindings: power: Add missing rpmpd smd performance level The RPM_SMD_LEVEL_TURBO_HIGH is used by MSM8956/8976 and APQ variants: add the definition. Signed-off-by: Angelo G. Del Regno Signed-off-by: Bjorn Andersson --- include/dt-bindings/power/qcom-rpmpd.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/dt-bindings/power/qcom-rpmpd.h b/include/dt-bindings/power/qcom-rpmpd.h index 93e36d011527..30a0aee0df57 100644 --- a/include/dt-bindings/power/qcom-rpmpd.h +++ b/include/dt-bindings/power/qcom-rpmpd.h @@ -68,6 +68,7 @@ #define RPM_SMD_LEVEL_NOM_PLUS 320 #define RPM_SMD_LEVEL_TURBO 384 #define RPM_SMD_LEVEL_TURBO_NO_CPR 416 +#define RPM_SMD_LEVEL_TURBO_HIGH 448 #define RPM_SMD_LEVEL_BINNING 512 #endif -- cgit v1.2.3-59-g8ed1b From b1d522443b4b000974e48f27d4ee77dbfc67962d Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Sat, 5 Oct 2019 13:07:58 +0200 Subject: soc: qcom: rpmpd: Add rpm power domains for msm8976 The MSM8956/76 SoCs have two main voltage-level power domains, VDD_CX and VDD_MX, which also have their own voltage-floor-level (VFL) corner. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/power/qcom,rpmpd.txt | 1 + drivers/soc/qcom/rpmpd.c | 23 ++++++++++++++++++++++ include/dt-bindings/power/qcom-rpmpd.h | 8 ++++++++ 3 files changed, 32 insertions(+) (limited to 'include') diff --git a/Documentation/devicetree/bindings/power/qcom,rpmpd.txt b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt index eb35b22f9e23..bc75bf49cdae 100644 --- a/Documentation/devicetree/bindings/power/qcom,rpmpd.txt +++ b/Documentation/devicetree/bindings/power/qcom,rpmpd.txt @@ -5,6 +5,7 @@ which then translates it into a corresponding voltage on a rail Required Properties: - compatible: Should be one of the following + * qcom,msm8976-rpmpd: RPM Power domain for the msm8976 family of SoC * qcom,msm8996-rpmpd: RPM Power domain for the msm8996 family of SoC * qcom,msm8998-rpmpd: RPM Power domain for the msm8998 family of SoC * qcom,qcs404-rpmpd: RPM Power domain for the qcs404 family of SoC diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index 3c1a55cf25d6..2b1834c5609a 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -115,6 +115,28 @@ struct rpmpd_desc { static DEFINE_MUTEX(rpmpd_lock); +/* msm8976 RPM Power Domains */ +DEFINE_RPMPD_PAIR(msm8976, vddcx, vddcx_ao, SMPA, LEVEL, 2); +DEFINE_RPMPD_PAIR(msm8976, vddmx, vddmx_ao, SMPA, LEVEL, 6); + +DEFINE_RPMPD_VFL(msm8976, vddcx_vfl, RWSC, 2); +DEFINE_RPMPD_VFL(msm8976, vddmx_vfl, RWSM, 6); + +static struct rpmpd *msm8976_rpmpds[] = { + [MSM8976_VDDCX] = &msm8976_vddcx, + [MSM8976_VDDCX_AO] = &msm8976_vddcx_ao, + [MSM8976_VDDCX_VFL] = &msm8976_vddcx_vfl, + [MSM8976_VDDMX] = &msm8976_vddmx, + [MSM8976_VDDMX_AO] = &msm8976_vddmx_ao, + [MSM8976_VDDMX_VFL] = &msm8976_vddmx_vfl, +}; + +static const struct rpmpd_desc msm8976_desc = { + .rpmpds = msm8976_rpmpds, + .num_pds = ARRAY_SIZE(msm8976_rpmpds), + .max_state = RPM_SMD_LEVEL_TURBO_HIGH, +}; + /* msm8996 RPM Power domains */ DEFINE_RPMPD_PAIR(msm8996, vddcx, vddcx_ao, SMPA, CORNER, 1); DEFINE_RPMPD_PAIR(msm8996, vddmx, vddmx_ao, SMPA, CORNER, 2); @@ -198,6 +220,7 @@ static const struct rpmpd_desc qcs404_desc = { }; static const struct of_device_id rpmpd_match_table[] = { + { .compatible = "qcom,msm8976-rpmpd", .data = &msm8976_desc }, { .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc }, { .compatible = "qcom,msm8998-rpmpd", .data = &msm8998_desc }, { .compatible = "qcom,qcs404-rpmpd", .data = &qcs404_desc }, diff --git a/include/dt-bindings/power/qcom-rpmpd.h b/include/dt-bindings/power/qcom-rpmpd.h index 30a0aee0df57..f05f8b1808ec 100644 --- a/include/dt-bindings/power/qcom-rpmpd.h +++ b/include/dt-bindings/power/qcom-rpmpd.h @@ -27,6 +27,14 @@ #define RPMH_REGULATOR_LEVEL_TURBO 384 #define RPMH_REGULATOR_LEVEL_TURBO_L1 416 +/* MSM8976 Power Domain Indexes */ +#define MSM8976_VDDCX 0 +#define MSM8976_VDDCX_AO 1 +#define MSM8976_VDDCX_VFL 2 +#define MSM8976_VDDMX 3 +#define MSM8976_VDDMX_AO 4 +#define MSM8976_VDDMX_VFL 5 + /* MSM8996 Power Domain Indexes */ #define MSM8996_VDDCX 0 #define MSM8996_VDDCX_AO 1 -- cgit v1.2.3-59-g8ed1b From 5d9730b9eb05a349c278a9f6f058ebefa9063def Mon Sep 17 00:00:00 2001 From: Xingyu Chen Date: Sun, 29 Sep 2019 14:24:14 +0800 Subject: dt-bindings: reset: add bindings for the Meson-A1 SoC Reset Controller Add DT bindings for the Meson-A1 SoC Reset Controller include file, and also slightly update documentation. Signed-off-by: Xingyu Chen Reviewed-by: Rob Herring Signed-off-by: Philipp Zabel --- .../bindings/reset/amlogic,meson-reset.yaml | 1 + include/dt-bindings/reset/amlogic,meson-a1-reset.h | 74 ++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 include/dt-bindings/reset/amlogic,meson-a1-reset.h (limited to 'include') diff --git a/Documentation/devicetree/bindings/reset/amlogic,meson-reset.yaml b/Documentation/devicetree/bindings/reset/amlogic,meson-reset.yaml index 00917d868d58..b3f57d81f007 100644 --- a/Documentation/devicetree/bindings/reset/amlogic,meson-reset.yaml +++ b/Documentation/devicetree/bindings/reset/amlogic,meson-reset.yaml @@ -16,6 +16,7 @@ properties: - amlogic,meson8b-reset # Reset Controller on Meson8b and compatible SoCs - amlogic,meson-gxbb-reset # Reset Controller on GXBB and compatible SoCs - amlogic,meson-axg-reset # Reset Controller on AXG and compatible SoCs + - amlogic,meson-a1-reset # Reset Controller on A1 and compatible SoCs reg: maxItems: 1 diff --git a/include/dt-bindings/reset/amlogic,meson-a1-reset.h b/include/dt-bindings/reset/amlogic,meson-a1-reset.h new file mode 100644 index 000000000000..f1a3a797540d --- /dev/null +++ b/include/dt-bindings/reset/amlogic,meson-a1-reset.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) + * + * Copyright (c) 2019 Amlogic, Inc. All rights reserved. + * Author: Xingyu Chen + * + */ + +#ifndef _DT_BINDINGS_AMLOGIC_MESON_A1_RESET_H +#define _DT_BINDINGS_AMLOGIC_MESON_A1_RESET_H + +/* RESET0 */ +/* 0 */ +#define RESET_AM2AXI_VAD 1 +/* 2-3 */ +#define RESET_PSRAM 4 +#define RESET_PAD_CTRL 5 +/* 6 */ +#define RESET_TEMP_SENSOR 7 +#define RESET_AM2AXI_DEV 8 +/* 9 */ +#define RESET_SPICC_A 10 +#define RESET_MSR_CLK 11 +#define RESET_AUDIO 12 +#define RESET_ANALOG_CTRL 13 +#define RESET_SAR_ADC 14 +#define RESET_AUDIO_VAD 15 +#define RESET_CEC 16 +#define RESET_PWM_EF 17 +#define RESET_PWM_CD 18 +#define RESET_PWM_AB 19 +/* 20 */ +#define RESET_IR_CTRL 21 +#define RESET_I2C_S_A 22 +/* 23 */ +#define RESET_I2C_M_D 24 +#define RESET_I2C_M_C 25 +#define RESET_I2C_M_B 26 +#define RESET_I2C_M_A 27 +#define RESET_I2C_PROD_AHB 28 +#define RESET_I2C_PROD 29 +/* 30-31 */ + +/* RESET1 */ +#define RESET_ACODEC 32 +#define RESET_DMA 33 +#define RESET_SD_EMMC_A 34 +/* 35 */ +#define RESET_USBCTRL 36 +/* 37 */ +#define RESET_USBPHY 38 +/* 39-41 */ +#define RESET_RSA 42 +#define RESET_DMC 43 +/* 44 */ +#define RESET_IRQ_CTRL 45 +/* 46 */ +#define RESET_NIC_VAD 47 +#define RESET_NIC_AXI 48 +#define RESET_RAMA 49 +#define RESET_RAMB 50 +/* 51-52 */ +#define RESET_ROM 53 +#define RESET_SPIFC 54 +#define RESET_GIC 55 +#define RESET_UART_C 56 +#define RESET_UART_B 57 +#define RESET_UART_A 58 +#define RESET_OSC_RING 59 +/* 60-63 */ + +/* RESET2 */ +/* 64-95 */ + +#endif -- cgit v1.2.3-59-g8ed1b From 89da2ba947b1080199f4a6413686569a75fc2e7d Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 8 Oct 2019 15:16:14 +0800 Subject: soc: qcom: Fix llcc-qcom definitions to include commit 99356b03b431 ("soc: qcom: Make llcc-qcom a generic driver") move these out of llcc-qcom.h, make the building fails: drivers/edac/qcom_edac.c:86:40: error: array type has incomplete element type struct llcc_edac_reg_data static const struct llcc_edac_reg_data edac_reg_data[] = { ^~~~~~~~~~~~~ drivers/edac/qcom_edac.c:87:3: error: array index in non-array initializer [LLCC_DRAM_CE] = { ^~~~~~~~~~~~ drivers/edac/qcom_edac.c:87:3: note: (near initialization for edac_reg_data) drivers/edac/qcom_edac.c:88:3: error: field name not in record or union initializer .name = "DRAM Single-bit", ... drivers/edac/qcom_edac.c:169:51: warning: struct llcc_drv_data declared inside parameter list will not be visible outside of this definition or declaration qcom_llcc_clear_error_status(int err_type, struct llcc_drv_data *drv) ^~~~~~~~~~~~~ This patch move the needed definitions back to include. Reported-by: Hulk Robot Fixes: 99356b03b431 ("soc: qcom: Make llcc-qcom a generic driver") Signed-off-by: YueHaibing Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/llcc-qcom.c | 50 -------------------------------------- include/linux/soc/qcom/llcc-qcom.h | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 50 deletions(-) (limited to 'include') diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index 98563ef0ac6b..436067367db4 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -86,56 +86,6 @@ struct llcc_slice_config { bool activate_on_init; }; -/** - * llcc_drv_data - Data associated with the llcc driver - * @regmap: regmap associated with the llcc device - * @bcast_regmap: regmap associated with llcc broadcast offset - * @cfg: pointer to the data structure for slice configuration - * @lock: mutex associated with each slice - * @cfg_size: size of the config data table - * @max_slices: max slices as read from device tree - * @num_banks: Number of llcc banks - * @bitmap: Bit map to track the active slice ids - * @offsets: Pointer to the bank offsets array - * @ecc_irq: interrupt for llcc cache error detection and reporting - */ -struct llcc_drv_data { - struct regmap *regmap; - struct regmap *bcast_regmap; - const struct llcc_slice_config *cfg; - struct mutex lock; - u32 cfg_size; - u32 max_slices; - u32 num_banks; - unsigned long *bitmap; - u32 *offsets; - int ecc_irq; -}; - -/** - * llcc_edac_reg_data - llcc edac registers data for each error type - * @name: Name of the error - * @synd_reg: Syndrome register address - * @count_status_reg: Status register address to read the error count - * @ways_status_reg: Status register address to read the error ways - * @reg_cnt: Number of registers - * @count_mask: Mask value to get the error count - * @ways_mask: Mask value to get the error ways - * @count_shift: Shift value to get the error count - * @ways_shift: Shift value to get the error ways - */ -struct llcc_edac_reg_data { - char *name; - u64 synd_reg; - u64 count_status_reg; - u64 ways_status_reg; - u32 reg_cnt; - u32 count_mask; - u32 ways_mask; - u8 count_shift; - u8 ways_shift; -}; - struct qcom_llcc_config { const struct llcc_slice_config *sct_data; int size; diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h index c0acdb28fde8..90b864655822 100644 --- a/include/linux/soc/qcom/llcc-qcom.h +++ b/include/linux/soc/qcom/llcc-qcom.h @@ -37,6 +37,56 @@ struct llcc_slice_desc { size_t slice_size; }; +/** + * llcc_edac_reg_data - llcc edac registers data for each error type + * @name: Name of the error + * @synd_reg: Syndrome register address + * @count_status_reg: Status register address to read the error count + * @ways_status_reg: Status register address to read the error ways + * @reg_cnt: Number of registers + * @count_mask: Mask value to get the error count + * @ways_mask: Mask value to get the error ways + * @count_shift: Shift value to get the error count + * @ways_shift: Shift value to get the error ways + */ +struct llcc_edac_reg_data { + char *name; + u64 synd_reg; + u64 count_status_reg; + u64 ways_status_reg; + u32 reg_cnt; + u32 count_mask; + u32 ways_mask; + u8 count_shift; + u8 ways_shift; +}; + +/** + * llcc_drv_data - Data associated with the llcc driver + * @regmap: regmap associated with the llcc device + * @bcast_regmap: regmap associated with llcc broadcast offset + * @cfg: pointer to the data structure for slice configuration + * @lock: mutex associated with each slice + * @cfg_size: size of the config data table + * @max_slices: max slices as read from device tree + * @num_banks: Number of llcc banks + * @bitmap: Bit map to track the active slice ids + * @offsets: Pointer to the bank offsets array + * @ecc_irq: interrupt for llcc cache error detection and reporting + */ +struct llcc_drv_data { + struct regmap *regmap; + struct regmap *bcast_regmap; + const struct llcc_slice_config *cfg; + struct mutex lock; + u32 cfg_size; + u32 max_slices; + u32 num_banks; + unsigned long *bitmap; + u32 *offsets; + int ecc_irq; +}; + #if IS_ENABLED(CONFIG_QCOM_LLCC) /** * llcc_slice_getd - get llcc slice descriptor -- cgit v1.2.3-59-g8ed1b From d30cd83f68535ca21412b1abe8684438690c1c2b Mon Sep 17 00:00:00 2001 From: Tero Kristo Date: Wed, 9 Oct 2019 08:55:38 -0700 Subject: soc: ti: omap-prm: add support for denying idle for reset clockdomain TI SoCs hardware reset signals require the parent clockdomain to be in force wakeup mode while de-asserting the reset, otherwise it may never complete. To support this, add pdata hooks to control the clockdomain directly. Signed-off-by: Tero Kristo Reviewed-by: Tony Lindgren Signed-off-by: Santosh Shilimkar --- drivers/soc/ti/omap_prm.c | 36 ++++++++++++++++++++++++++++++++++-- include/linux/platform_data/ti-prm.h | 21 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 include/linux/platform_data/ti-prm.h (limited to 'include') diff --git a/drivers/soc/ti/omap_prm.c b/drivers/soc/ti/omap_prm.c index a3ffb19e402f..09985da8f6d0 100644 --- a/drivers/soc/ti/omap_prm.c +++ b/drivers/soc/ti/omap_prm.c @@ -16,6 +16,8 @@ #include #include +#include + struct omap_rst_map { s8 rst; s8 st; @@ -24,6 +26,7 @@ struct omap_rst_map { struct omap_prm_data { u32 base; const char *name; + const char *clkdm_name; u16 rstctrl; u16 rstst; const struct omap_rst_map *rstmap; @@ -40,6 +43,8 @@ struct omap_reset_data { struct omap_prm *prm; u32 mask; spinlock_t lock; + struct clockdomain *clkdm; + struct device *dev; }; #define to_omap_reset_data(p) container_of((p), struct omap_reset_data, rcdev) @@ -49,6 +54,7 @@ struct omap_reset_data { #define OMAP_PRM_HAS_RSTCTRL BIT(0) #define OMAP_PRM_HAS_RSTST BIT(1) +#define OMAP_PRM_HAS_NO_CLKDM BIT(2) #define OMAP_PRM_HAS_RESETS (OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_RSTST) @@ -133,6 +139,8 @@ static int omap_reset_deassert(struct reset_controller_dev *rcdev, int st_bit; bool has_rstst; unsigned long flags; + struct ti_prm_platform_data *pdata = dev_get_platdata(reset->dev); + int ret = 0; has_rstst = reset->prm->data->rstst || (reset->prm->data->flags & OMAP_PRM_HAS_RSTST); @@ -145,6 +153,9 @@ static int omap_reset_deassert(struct reset_controller_dev *rcdev, writel_relaxed(v, reset->prm->base + reset->prm->data->rstst); } + if (reset->clkdm) + pdata->clkdm_deny_idle(reset->clkdm); + /* de-assert the reset control line */ spin_lock_irqsave(&reset->lock, flags); v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl); @@ -153,7 +164,7 @@ static int omap_reset_deassert(struct reset_controller_dev *rcdev, spin_unlock_irqrestore(&reset->lock, flags); if (!has_rstst) - return 0; + goto exit; /* wait for the status to be set */ ret = readl_relaxed_poll_timeout(reset->prm->base + @@ -164,7 +175,11 @@ static int omap_reset_deassert(struct reset_controller_dev *rcdev, pr_err("%s: timedout waiting for %s:%lu\n", __func__, reset->prm->data->name, id); - return 0; +exit: + if (reset->clkdm) + pdata->clkdm_allow_idle(reset->clkdm); + + return ret; } static const struct reset_control_ops omap_reset_ops = { @@ -189,6 +204,8 @@ static int omap_prm_reset_init(struct platform_device *pdev, { struct omap_reset_data *reset; const struct omap_rst_map *map; + struct ti_prm_platform_data *pdata = dev_get_platdata(&pdev->dev); + char buf[32]; /* * Check if we have controllable resets. If either rstctrl is non-zero @@ -198,6 +215,11 @@ static int omap_prm_reset_init(struct platform_device *pdev, if (!prm->data->rstctrl && !(prm->data->flags & OMAP_PRM_HAS_RSTCTRL)) return 0; + /* Check if we have the pdata callbacks in place */ + if (!pdata || !pdata->clkdm_lookup || !pdata->clkdm_deny_idle || + !pdata->clkdm_allow_idle) + return -EINVAL; + map = prm->data->rstmap; if (!map) return -EINVAL; @@ -212,10 +234,20 @@ static int omap_prm_reset_init(struct platform_device *pdev, reset->rcdev.nr_resets = OMAP_MAX_RESETS; reset->rcdev.of_xlate = omap_prm_reset_xlate; reset->rcdev.of_reset_n_cells = 1; + reset->dev = &pdev->dev; spin_lock_init(&reset->lock); reset->prm = prm; + sprintf(buf, "%s_clkdm", prm->data->clkdm_name ? prm->data->clkdm_name : + prm->data->name); + + if (!(prm->data->flags & OMAP_PRM_HAS_NO_CLKDM)) { + reset->clkdm = pdata->clkdm_lookup(buf); + if (!reset->clkdm) + return -EINVAL; + } + while (map->rst >= 0) { reset->mask |= BIT(map->rst); map++; diff --git a/include/linux/platform_data/ti-prm.h b/include/linux/platform_data/ti-prm.h new file mode 100644 index 000000000000..28154c3226c2 --- /dev/null +++ b/include/linux/platform_data/ti-prm.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * TI PRM (Power & Reset Manager) platform data + * + * Copyright (C) 2019 Texas Instruments, Inc. + * + * Tero Kristo + */ + +#ifndef _LINUX_PLATFORM_DATA_TI_PRM_H +#define _LINUX_PLATFORM_DATA_TI_PRM_H + +struct clockdomain; + +struct ti_prm_platform_data { + void (*clkdm_deny_idle)(struct clockdomain *clkdm); + void (*clkdm_allow_idle)(struct clockdomain *clkdm); + struct clockdomain * (*clkdm_lookup)(const char *name); +}; + +#endif /* _LINUX_PLATFORM_DATA_TI_PRM_H */ -- cgit v1.2.3-59-g8ed1b From e502ff8606b32df4f9f2435ab00278312db125b3 Mon Sep 17 00:00:00 2001 From: Tejas Patel Date: Mon, 26 Aug 2019 13:30:44 -0700 Subject: soc: xilinx: Set CAP_UNUSABLE requirement for versal while powering down domain For "0" requirement which is used to inform firmware that device is not required currently by master, Versal PLM (Platform Loader and Manager) which runs on Platform Management Controller and is responsible platform management of devices that disables clock, power it down and reset the device. genpd_power_off() is being called during runtime suspend also. So, if any device goes to runtime suspend state during resumes it needs to be re-initialized again. It is possible that drivers do not reinitialize device upon resume from runtime suspend every time ans so dont want it to be powered down or get reset during runtime suspend. In Versal PLM new PM_CAP_UNUSABLE capability is added, which disables clock only and avoids power down and reset during runtime suspend. Power and reset will be gated with core suspend.So, this patch sets CAPABILITY_UNUSABLE requirement during gpd_power_off() if platform is other than zynqmp. Signed-off-by: Tejas Patel Signed-off-by: Jolly Shah Signed-off-by: Michal Simek --- drivers/soc/xilinx/zynqmp_pm_domains.c | 10 ++++++++-- include/linux/firmware/xlnx-zynqmp.h | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/soc/xilinx/zynqmp_pm_domains.c b/drivers/soc/xilinx/zynqmp_pm_domains.c index 600f57cf0c2e..23d90cb12ba9 100644 --- a/drivers/soc/xilinx/zynqmp_pm_domains.c +++ b/drivers/soc/xilinx/zynqmp_pm_domains.c @@ -2,7 +2,7 @@ /* * ZynqMP Generic PM domain support * - * Copyright (C) 2015-2018 Xilinx, Inc. + * Copyright (C) 2015-2019 Xilinx, Inc. * * Davorin Mista * Jolly Shah @@ -25,6 +25,8 @@ static const struct zynqmp_eemi_ops *eemi_ops; +static int min_capability; + /** * struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain * @gpd: Generic power domain @@ -106,7 +108,7 @@ static int zynqmp_gpd_power_off(struct generic_pm_domain *domain) int ret; struct pm_domain_data *pdd, *tmp; struct zynqmp_pm_domain *pd; - u32 capabilities = 0; + u32 capabilities = min_capability; bool may_wakeup; if (!eemi_ops->set_requirement) @@ -283,6 +285,10 @@ static int zynqmp_gpd_probe(struct platform_device *pdev) if (!domains) return -ENOMEM; + if (!of_device_is_compatible(dev->parent->of_node, + "xlnx,zynqmp-firmware")) + min_capability = ZYNQMP_PM_CAPABILITY_UNUSABLE; + for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++, pd++) { pd->node_id = 0; pd->gpd.name = kasprintf(GFP_KERNEL, "domain%d", i); diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h index 778abbbc7d94..adb14bcedca2 100644 --- a/include/linux/firmware/xlnx-zynqmp.h +++ b/include/linux/firmware/xlnx-zynqmp.h @@ -2,7 +2,7 @@ /* * Xilinx Zynq MPSoC Firmware layer * - * Copyright (C) 2014-2018 Xilinx + * Copyright (C) 2014-2019 Xilinx * * Michal Simek * Davorin Mista @@ -46,6 +46,7 @@ #define ZYNQMP_PM_CAPABILITY_ACCESS 0x1U #define ZYNQMP_PM_CAPABILITY_CONTEXT 0x2U #define ZYNQMP_PM_CAPABILITY_WAKEUP 0x4U +#define ZYNQMP_PM_CAPABILITY_UNUSABLE 0x8U /* * Firmware FPGA Manager flags -- cgit v1.2.3-59-g8ed1b From a362687404edc5d73a4fc281af3b2b1542ef194e Mon Sep 17 00:00:00 2001 From: Olof Johansson Date: Mon, 21 Oct 2019 18:52:24 -0700 Subject: soc: mmp: guard include of asm/cputype.h with CONFIG_ARM{,64} Since this driver is enabled for COMPILE_TEST, it avoids build error on x86 allmodconfig: In file included from /build/drivers/phy/marvell/phy-mmp3-usb.c:12: /build/include/linux/soc/mmp/cputype.h:5:10: fatal error: asm/cputype.h: No such file or directory Link: https://lore.kernel.org/r/20191022015658.14624-1-olof@lixom.net Signed-off-by: Olof Johansson --- include/linux/soc/mmp/cputype.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include') diff --git a/include/linux/soc/mmp/cputype.h b/include/linux/soc/mmp/cputype.h index c3ec88983e94..221790761e8e 100644 --- a/include/linux/soc/mmp/cputype.h +++ b/include/linux/soc/mmp/cputype.h @@ -2,7 +2,9 @@ #ifndef __ASM_MACH_CPUTYPE_H #define __ASM_MACH_CPUTYPE_H +#if defined(CONFIG_ARM) || defined(CONFIG_ARM64) #include +#endif /* * CPU Stepping CPU_ID CHIP_ID -- cgit v1.2.3-59-g8ed1b From 771fdcf8d3d04e77ae0f0dc1018144206a61d216 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 16 Oct 2019 16:57:53 +0200 Subject: PM / OPP: Support adjusting OPP voltages at runtime On some SoCs the Adaptive Voltage Scaling (AVS) technique is employed to optimize the operating voltage of a device. At a given frequency, the hardware monitors dynamic factors and either makes a suggestion for how much to adjust a voltage for the current frequency, or it automatically adjusts the voltage without software intervention. Add an API to the OPP library for the former case, so that AVS type devices can update the voltages for an OPP when the hardware determines the voltage should change. The assumption is that drivers like CPUfreq or devfreq will register for the OPP notifiers and adjust the voltage according to suggestions that AVS makes. This patch is derived from [1] submitted by Stephen. [1] https://lore.kernel.org/patchwork/patch/599279/ Signed-off-by: Stephen Boyd Signed-off-by: Roger Lu [s.nawrocki@samsung.com: added handling of OPP min/max voltage] Signed-off-by: Sylwester Nawrocki Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 13 ++++++++++ 2 files changed, 82 insertions(+) (limited to 'include') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 3b7ffd0234e9..f38b3be85072 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -2112,6 +2112,75 @@ put_table: return r; } +/** + * dev_pm_opp_adjust_voltage() - helper to change the voltage of an OPP + * @dev: device for which we do this operation + * @freq: OPP frequency to adjust voltage of + * @u_volt: new OPP target voltage + * @u_volt_min: new OPP min voltage + * @u_volt_max: new OPP max voltage + * + * Return: -EINVAL for bad pointers, -ENOMEM if no memory available for the + * copy operation, returns 0 if no modifcation was done OR modification was + * successful. + */ +int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq, + unsigned long u_volt, unsigned long u_volt_min, + unsigned long u_volt_max) + +{ + struct opp_table *opp_table; + struct dev_pm_opp *tmp_opp, *opp = ERR_PTR(-ENODEV); + int r = 0; + + /* Find the opp_table */ + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) { + r = PTR_ERR(opp_table); + dev_warn(dev, "%s: Device OPP not found (%d)\n", __func__, r); + return r; + } + + mutex_lock(&opp_table->lock); + + /* Do we have the frequency? */ + list_for_each_entry(tmp_opp, &opp_table->opp_list, node) { + if (tmp_opp->rate == freq) { + opp = tmp_opp; + break; + } + } + + if (IS_ERR(opp)) { + r = PTR_ERR(opp); + goto adjust_unlock; + } + + /* Is update really needed? */ + if (opp->supplies->u_volt == u_volt) + goto adjust_unlock; + + opp->supplies->u_volt = u_volt; + opp->supplies->u_volt_min = u_volt_min; + opp->supplies->u_volt_max = u_volt_max; + + dev_pm_opp_get(opp); + mutex_unlock(&opp_table->lock); + + /* Notify the voltage change of the OPP */ + blocking_notifier_call_chain(&opp_table->head, OPP_EVENT_ADJUST_VOLTAGE, + opp); + + dev_pm_opp_put(opp); + goto adjust_put_table; + +adjust_unlock: + mutex_unlock(&opp_table->lock); +adjust_put_table: + dev_pm_opp_put_opp_table(opp_table); + return r; +} + /** * dev_pm_opp_enable() - Enable a specific OPP * @dev: device for which we do this operation diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index b8197ab014f2..747861816f4f 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -22,6 +22,7 @@ struct opp_table; enum dev_pm_opp_event { OPP_EVENT_ADD, OPP_EVENT_REMOVE, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE, + OPP_EVENT_ADJUST_VOLTAGE, }; /** @@ -113,6 +114,10 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, void dev_pm_opp_remove(struct device *dev, unsigned long freq); void dev_pm_opp_remove_all_dynamic(struct device *dev); +int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq, + unsigned long u_volt, unsigned long u_volt_min, + unsigned long u_volt_max); + int dev_pm_opp_enable(struct device *dev, unsigned long freq); int dev_pm_opp_disable(struct device *dev, unsigned long freq); @@ -242,6 +247,14 @@ static inline void dev_pm_opp_remove_all_dynamic(struct device *dev) { } +static inline int +dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq, + unsigned long u_volt, unsigned long u_volt_min, + unsigned long u_volt_max) +{ + return 0; +} + static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq) { return 0; -- cgit v1.2.3-59-g8ed1b From a48108c0c20f02485b8cc3ca83652a55a0f5e47f Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 22 Oct 2019 16:51:37 +0200 Subject: reset: improve of_xlate documentation Mention of_reset_simple_xlate as the default if of_xlate is not set. Signed-off-by: Philipp Zabel --- drivers/reset/core.c | 6 ++++-- include/linux/reset-controller.h | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/reset/core.c b/drivers/reset/core.c index 2badff33a0db..329c78599a02 100644 --- a/drivers/reset/core.c +++ b/drivers/reset/core.c @@ -78,8 +78,10 @@ static const char *rcdev_name(struct reset_controller_dev *rcdev) * @reset_spec: reset line specifier as found in the device tree * @flags: a flags pointer to fill in (optional) * - * This simple translation function should be used for reset controllers - * with 1:1 mapping, where reset lines can be indexed by number without gaps. + * This static translation function is used by default if of_xlate in + * :c:type:`reset_controller_dev` is not set. It is useful for all reset + * controllers with 1:1 mapping, where reset lines can be indexed by number + * without gaps. */ static int of_reset_simple_xlate(struct reset_controller_dev *rcdev, const struct of_phandle_args *reset_spec) diff --git a/include/linux/reset-controller.h b/include/linux/reset-controller.h index 9326d671b6e6..8d35753d419e 100644 --- a/include/linux/reset-controller.h +++ b/include/linux/reset-controller.h @@ -62,7 +62,8 @@ struct reset_control_lookup { * @of_node: corresponding device tree node as phandle target * @of_reset_n_cells: number of cells in reset line specifiers * @of_xlate: translation function to translate from specifier as found in the - * device tree to id as given to the reset control ops + * device tree to id as given to the reset control ops, defaults + * to :c:func:`of_reset_simple_xlate`. * @nr_resets: number of reset controls in this reset controller device */ struct reset_controller_dev { -- cgit v1.2.3-59-g8ed1b From c2ffa00ad6152ad54940f942fc316b9c83d5e6f9 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Tue, 22 Oct 2019 16:53:25 +0200 Subject: reset: document (devm_)reset_control_get_optional variants Add kerneldoc comments for the optional reset_control_get variants. Signed-off-by: Philipp Zabel --- include/linux/reset.h | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'include') diff --git a/include/linux/reset.h b/include/linux/reset.h index e7793fc0fa93..bf7c7f188406 100644 --- a/include/linux/reset.h +++ b/include/linux/reset.h @@ -203,12 +203,34 @@ static inline struct reset_control *reset_control_get_shared( return __reset_control_get(dev, id, 0, true, false, false); } +/** + * reset_control_get_optional_exclusive - optional reset_control_get_exclusive() + * @dev: device to be reset by the controller + * @id: reset line name + * + * Optional variant of reset_control_get_exclusive(). If the requested reset + * is not specified in the device tree, this function returns NULL instead of + * an error. + * + * See reset_control_get_exclusive() for more information. + */ static inline struct reset_control *reset_control_get_optional_exclusive( struct device *dev, const char *id) { return __reset_control_get(dev, id, 0, false, true, true); } +/** + * reset_control_get_optional_shared - optional reset_control_get_shared() + * @dev: device to be reset by the controller + * @id: reset line name + * + * Optional variant of reset_control_get_shared(). If the requested reset + * is not specified in the device tree, this function returns NULL instead of + * an error. + * + * See reset_control_get_shared() for more information. + */ static inline struct reset_control *reset_control_get_optional_shared( struct device *dev, const char *id) { @@ -354,12 +376,36 @@ static inline struct reset_control *devm_reset_control_get_shared( return __devm_reset_control_get(dev, id, 0, true, false, false); } +/** + * devm_reset_control_get_optional_exclusive - resource managed + * reset_control_get_optional_exclusive() + * @dev: device to be reset by the controller + * @id: reset line name + * + * Managed reset_control_get_optional_exclusive(). For reset controllers + * returned from this function, reset_control_put() is called automatically on + * driver detach. + * + * See reset_control_get_optional_exclusive() for more information. + */ static inline struct reset_control *devm_reset_control_get_optional_exclusive( struct device *dev, const char *id) { return __devm_reset_control_get(dev, id, 0, false, true, true); } +/** + * devm_reset_control_get_optional_shared - resource managed + * reset_control_get_optional_shared() + * @dev: device to be reset by the controller + * @id: reset line name + * + * Managed reset_control_get_optional_shared(). For reset controllers returned + * from this function, reset_control_put() is called automatically on driver + * detach. + * + * See reset_control_get_optional_shared() for more information. + */ static inline struct reset_control *devm_reset_control_get_optional_shared( struct device *dev, const char *id) { -- cgit v1.2.3-59-g8ed1b From b4941adb24c0676f77ddc25e6d7836b8245c47fc Mon Sep 17 00:00:00 2001 From: Ran Wang Date: Thu, 24 Oct 2019 17:26:42 +0800 Subject: PM: wakeup: Add routine to help fetch wakeup source object. Some user might want to go through all registered wakeup sources and doing things accordingly. For example, SoC PM driver might need to do HW programming to prevent powering down specific IP which wakeup source depending on. So add this API to help walk through all registered wakeup source objects on that list and return them one by one. Signed-off-by: Ran Wang Tested-by: Leonard Crestez Reviewed-by: Rafael J. Wysocki Signed-off-by: Li Yang --- drivers/base/power/wakeup.c | 54 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_wakeup.h | 9 ++++++++ 2 files changed, 63 insertions(+) (limited to 'include') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 5817b51d2b15..70a9edb5f525 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -247,6 +247,60 @@ void wakeup_source_unregister(struct wakeup_source *ws) } EXPORT_SYMBOL_GPL(wakeup_source_unregister); +/** + * wakeup_sources_read_lock - Lock wakeup source list for read. + * + * Returns an index of srcu lock for struct wakeup_srcu. + * This index must be passed to the matching wakeup_sources_read_unlock(). + */ +int wakeup_sources_read_lock(void) +{ + return srcu_read_lock(&wakeup_srcu); +} +EXPORT_SYMBOL_GPL(wakeup_sources_read_lock); + +/** + * wakeup_sources_read_unlock - Unlock wakeup source list. + * @idx: return value from corresponding wakeup_sources_read_lock() + */ +void wakeup_sources_read_unlock(int idx) +{ + srcu_read_unlock(&wakeup_srcu, idx); +} +EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); + +/** + * wakeup_sources_walk_start - Begin a walk on wakeup source list + * + * Returns first object of the list of wakeup sources. + * + * Note that to be safe, wakeup sources list needs to be locked by calling + * wakeup_source_read_lock() for this. + */ +struct wakeup_source *wakeup_sources_walk_start(void) +{ + struct list_head *ws_head = &wakeup_sources; + + return list_entry_rcu(ws_head->next, struct wakeup_source, entry); +} +EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); + +/** + * wakeup_sources_walk_next - Get next wakeup source from the list + * @ws: Previous wakeup source object + * + * Note that to be safe, wakeup sources list needs to be locked by calling + * wakeup_source_read_lock() for this. + */ +struct wakeup_source *wakeup_sources_walk_next(struct wakeup_source *ws) +{ + struct list_head *ws_head = &wakeup_sources; + + return list_next_or_null_rcu(ws_head, &ws->entry, + struct wakeup_source, entry); +} +EXPORT_SYMBOL_GPL(wakeup_sources_walk_next); + /** * device_wakeup_attach - Attach a wakeup source object to a device object. * @dev: Device to handle. diff --git a/include/linux/pm_wakeup.h b/include/linux/pm_wakeup.h index 661efa029c96..aa3da6611533 100644 --- a/include/linux/pm_wakeup.h +++ b/include/linux/pm_wakeup.h @@ -63,6 +63,11 @@ struct wakeup_source { bool autosleep_enabled:1; }; +#define for_each_wakeup_source(ws) \ + for ((ws) = wakeup_sources_walk_start(); \ + (ws); \ + (ws) = wakeup_sources_walk_next((ws))) + #ifdef CONFIG_PM_SLEEP /* @@ -92,6 +97,10 @@ extern void wakeup_source_remove(struct wakeup_source *ws); extern struct wakeup_source *wakeup_source_register(struct device *dev, const char *name); extern void wakeup_source_unregister(struct wakeup_source *ws); +extern int wakeup_sources_read_lock(void); +extern void wakeup_sources_read_unlock(int idx); +extern struct wakeup_source *wakeup_sources_walk_start(void); +extern struct wakeup_source *wakeup_sources_walk_next(struct wakeup_source *ws); extern int device_wakeup_enable(struct device *dev); extern int device_wakeup_disable(struct device *dev); extern void device_set_wakeup_capable(struct device *dev, bool capable); -- cgit v1.2.3-59-g8ed1b From b8104fda1fff0882e43b7e98832a76d7e98eb3e9 Mon Sep 17 00:00:00 2001 From: John Garry Date: Tue, 5 Nov 2019 01:22:16 +0800 Subject: logic_pio: Define PIO_INDIRECT_SIZE for !CONFIG_INDIRECT_PIO With the goal of expanding the test coverage of the HiSi LPC driver to !ARM64, define a dummy PIO_INDIRECT_SIZE for !CONFIG_INDIRECT_PIO, which is required by the named driver. Signed-off-by: John Garry Signed-off-by: Wei Xu --- include/linux/logic_pio.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/logic_pio.h b/include/linux/logic_pio.h index 88e1e6304a71..54945aa824b4 100644 --- a/include/linux/logic_pio.h +++ b/include/linux/logic_pio.h @@ -108,10 +108,10 @@ void logic_outsl(unsigned long addr, const void *buffer, unsigned int count); * area by redefining the macro below. */ #define PIO_INDIRECT_SIZE 0x4000 -#define MMIO_UPPER_LIMIT (IO_SPACE_LIMIT - PIO_INDIRECT_SIZE) #else -#define MMIO_UPPER_LIMIT IO_SPACE_LIMIT +#define PIO_INDIRECT_SIZE 0 #endif /* CONFIG_INDIRECT_PIO */ +#define MMIO_UPPER_LIMIT (IO_SPACE_LIMIT - PIO_INDIRECT_SIZE) struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode); unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode, -- cgit v1.2.3-59-g8ed1b From dbbf98392af6e2cf3673908c1388ca1ae915c8bb Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Fri, 6 Sep 2019 15:06:41 +0000 Subject: memory: atmel-ebi: move NUM_CS definition inside EBI driver The total number of EBI CS lines is described by the EBI controller and not by the Matrix. Move the definition for the number of CS inside EBI driver. Signed-off-by: Tudor Ambarus Link: https://lore.kernel.org/r/20190906150632.19039-1-tudor.ambarus@microchip.com Signed-off-by: Alexandre Belloni --- drivers/memory/atmel-ebi.c | 6 ++++-- include/linux/mfd/syscon/atmel-matrix.h | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/memory/atmel-ebi.c b/drivers/memory/atmel-ebi.c index 0322df9dc249..8515196c2b03 100644 --- a/drivers/memory/atmel-ebi.c +++ b/drivers/memory/atmel-ebi.c @@ -19,6 +19,8 @@ #include #include +#define AT91_EBI_NUM_CS 8 + struct atmel_ebi_dev_config { int cs; struct atmel_smc_cs_conf smcconf; @@ -314,7 +316,7 @@ static int atmel_ebi_dev_setup(struct atmel_ebi *ebi, struct device_node *np, if (ret) return ret; - if (cs >= AT91_MATRIX_EBI_NUM_CS || + if (cs >= AT91_EBI_NUM_CS || !(ebi->caps->available_cs & BIT(cs))) { dev_err(dev, "invalid reg property in %pOF\n", np); return -EINVAL; @@ -344,7 +346,7 @@ static int atmel_ebi_dev_setup(struct atmel_ebi *ebi, struct device_node *np, apply = true; i = 0; - for_each_set_bit(cs, &cslines, AT91_MATRIX_EBI_NUM_CS) { + for_each_set_bit(cs, &cslines, AT91_EBI_NUM_CS) { ebid->configs[i].cs = cs; if (apply) { diff --git a/include/linux/mfd/syscon/atmel-matrix.h b/include/linux/mfd/syscon/atmel-matrix.h index f61cd127a852..20c25665216a 100644 --- a/include/linux/mfd/syscon/atmel-matrix.h +++ b/include/linux/mfd/syscon/atmel-matrix.h @@ -106,7 +106,6 @@ #define AT91_MATRIX_DDR_IOSR BIT(18) #define AT91_MATRIX_NFD0_SELECT BIT(24) #define AT91_MATRIX_DDR_MP_EN BIT(25) -#define AT91_MATRIX_EBI_NUM_CS 8 #define AT91_MATRIX_USBPUCR_PUON BIT(30) -- cgit v1.2.3-59-g8ed1b From e34212c75a68990f7215d64d725c61e57ca70357 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 12 Aug 2019 00:00:40 +0300 Subject: memory: tegra: Introduce Tegra30 EMC driver Introduce driver for the External Memory Controller (EMC) found on Tegra30 chips, it controls the external DRAM on the board. The purpose of this driver is to program memory timing for external memory on the EMC clock rate change. Acked-by: Peter De Schrijver Signed-off-by: Dmitry Osipenko Tested-by: Peter Geis Signed-off-by: Thierry Reding --- drivers/memory/tegra/Kconfig | 10 + drivers/memory/tegra/Makefile | 1 + drivers/memory/tegra/mc.c | 9 +- drivers/memory/tegra/mc.h | 30 +- drivers/memory/tegra/tegra30-emc.c | 1232 ++++++++++++++++++++++++++++++++++++ drivers/memory/tegra/tegra30.c | 42 ++ include/soc/tegra/mc.h | 2 +- 7 files changed, 1311 insertions(+), 15 deletions(-) create mode 100644 drivers/memory/tegra/tegra30-emc.c (limited to 'include') diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig index 4680124ddcab..fbfbaada61a2 100644 --- a/drivers/memory/tegra/Kconfig +++ b/drivers/memory/tegra/Kconfig @@ -17,6 +17,16 @@ config TEGRA20_EMC This driver is required to change memory timings / clock rate for external memory. +config TEGRA30_EMC + bool "NVIDIA Tegra30 External Memory Controller driver" + default y + depends on TEGRA_MC && ARCH_TEGRA_3x_SOC + help + This driver is for the External Memory Controller (EMC) found on + Tegra30 chips. The EMC controls the external DRAM on the board. + This driver is required to change memory timings / clock rate for + external memory. + config TEGRA124_EMC bool "NVIDIA Tegra124 External Memory Controller driver" default y diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile index 3971a6b7c487..3d23c4261104 100644 --- a/drivers/memory/tegra/Makefile +++ b/drivers/memory/tegra/Makefile @@ -11,5 +11,6 @@ tegra-mc-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o obj-$(CONFIG_TEGRA_MC) += tegra-mc.o obj-$(CONFIG_TEGRA20_EMC) += tegra20-emc.o +obj-$(CONFIG_TEGRA30_EMC) += tegra30-emc.o obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index 322aa7e8b088..41ee420275f1 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -49,9 +49,6 @@ #define MC_EMEM_ADR_CFG 0x54 #define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0) -#define MC_TIMING_CONTROL 0xfc -#define MC_TIMING_UPDATE BIT(0) - static const struct of_device_id tegra_mc_of_match[] = { #ifdef CONFIG_ARCH_TEGRA_2x_SOC { .compatible = "nvidia,tegra20-mc-gart", .data = &tegra20_mc_soc }, @@ -308,7 +305,7 @@ static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc) return 0; } -void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate) +int tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate) { unsigned int i; struct tegra_mc_timing *timing = NULL; @@ -323,11 +320,13 @@ void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate) if (!timing) { dev_err(mc->dev, "no memory timing registered for rate %lu\n", rate); - return; + return -EINVAL; } for (i = 0; i < mc->soc->num_emem_regs; ++i) mc_writel(mc, timing->emem_data[i], mc->soc->emem_regs[i]); + + return 0; } unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc) diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h index f9353494b708..410efc4d7e7b 100644 --- a/drivers/memory/tegra/mc.h +++ b/drivers/memory/tegra/mc.h @@ -6,20 +6,32 @@ #ifndef MEMORY_TEGRA_MC_H #define MEMORY_TEGRA_MC_H +#include #include #include #include -#define MC_INT_DECERR_MTS (1 << 16) -#define MC_INT_SECERR_SEC (1 << 13) -#define MC_INT_DECERR_VPR (1 << 12) -#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) -#define MC_INT_INVALID_SMMU_PAGE (1 << 10) -#define MC_INT_ARBITRATION_EMEM (1 << 9) -#define MC_INT_SECURITY_VIOLATION (1 << 8) -#define MC_INT_INVALID_GART_PAGE (1 << 7) -#define MC_INT_DECERR_EMEM (1 << 6) +#define MC_INT_DECERR_MTS BIT(16) +#define MC_INT_SECERR_SEC BIT(13) +#define MC_INT_DECERR_VPR BIT(12) +#define MC_INT_INVALID_APB_ASID_UPDATE BIT(11) +#define MC_INT_INVALID_SMMU_PAGE BIT(10) +#define MC_INT_ARBITRATION_EMEM BIT(9) +#define MC_INT_SECURITY_VIOLATION BIT(8) +#define MC_INT_INVALID_GART_PAGE BIT(7) +#define MC_INT_DECERR_EMEM BIT(6) + +#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94 +#define MC_EMEM_ARB_OUTSTANDING_REQ_MAX_MASK 0x1ff +#define MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE BIT(30) +#define MC_EMEM_ARB_OUTSTANDING_REQ_LIMIT_ENABLE BIT(31) + +#define MC_EMEM_ARB_OVERRIDE 0xe8 +#define MC_EMEM_ARB_OVERRIDE_EACK_MASK 0x3 + +#define MC_TIMING_CONTROL 0xfc +#define MC_TIMING_UPDATE BIT(0) static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset) { diff --git a/drivers/memory/tegra/tegra30-emc.c b/drivers/memory/tegra/tegra30-emc.c new file mode 100644 index 000000000000..6929980bf907 --- /dev/null +++ b/drivers/memory/tegra/tegra30-emc.c @@ -0,0 +1,1232 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tegra30 External Memory Controller driver + * + * Based on downstream driver from NVIDIA and tegra124-emc.c + * Copyright (C) 2011-2014 NVIDIA Corporation + * + * Author: Dmitry Osipenko + * Copyright (C) 2019 GRATE-DRIVER project + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mc.h" + +#define EMC_INTSTATUS 0x000 +#define EMC_INTMASK 0x004 +#define EMC_DBG 0x008 +#define EMC_CFG 0x00c +#define EMC_REFCTRL 0x020 +#define EMC_TIMING_CONTROL 0x028 +#define EMC_RC 0x02c +#define EMC_RFC 0x030 +#define EMC_RAS 0x034 +#define EMC_RP 0x038 +#define EMC_R2W 0x03c +#define EMC_W2R 0x040 +#define EMC_R2P 0x044 +#define EMC_W2P 0x048 +#define EMC_RD_RCD 0x04c +#define EMC_WR_RCD 0x050 +#define EMC_RRD 0x054 +#define EMC_REXT 0x058 +#define EMC_WDV 0x05c +#define EMC_QUSE 0x060 +#define EMC_QRST 0x064 +#define EMC_QSAFE 0x068 +#define EMC_RDV 0x06c +#define EMC_REFRESH 0x070 +#define EMC_BURST_REFRESH_NUM 0x074 +#define EMC_PDEX2WR 0x078 +#define EMC_PDEX2RD 0x07c +#define EMC_PCHG2PDEN 0x080 +#define EMC_ACT2PDEN 0x084 +#define EMC_AR2PDEN 0x088 +#define EMC_RW2PDEN 0x08c +#define EMC_TXSR 0x090 +#define EMC_TCKE 0x094 +#define EMC_TFAW 0x098 +#define EMC_TRPAB 0x09c +#define EMC_TCLKSTABLE 0x0a0 +#define EMC_TCLKSTOP 0x0a4 +#define EMC_TREFBW 0x0a8 +#define EMC_QUSE_EXTRA 0x0ac +#define EMC_ODT_WRITE 0x0b0 +#define EMC_ODT_READ 0x0b4 +#define EMC_WEXT 0x0b8 +#define EMC_CTT 0x0bc +#define EMC_MRS_WAIT_CNT 0x0c8 +#define EMC_MRS 0x0cc +#define EMC_EMRS 0x0d0 +#define EMC_SELF_REF 0x0e0 +#define EMC_MRW 0x0e8 +#define EMC_XM2DQSPADCTRL3 0x0f8 +#define EMC_FBIO_SPARE 0x100 +#define EMC_FBIO_CFG5 0x104 +#define EMC_FBIO_CFG6 0x114 +#define EMC_CFG_RSV 0x120 +#define EMC_AUTO_CAL_CONFIG 0x2a4 +#define EMC_AUTO_CAL_INTERVAL 0x2a8 +#define EMC_AUTO_CAL_STATUS 0x2ac +#define EMC_STATUS 0x2b4 +#define EMC_CFG_2 0x2b8 +#define EMC_CFG_DIG_DLL 0x2bc +#define EMC_CFG_DIG_DLL_PERIOD 0x2c0 +#define EMC_CTT_DURATION 0x2d8 +#define EMC_CTT_TERM_CTRL 0x2dc +#define EMC_ZCAL_INTERVAL 0x2e0 +#define EMC_ZCAL_WAIT_CNT 0x2e4 +#define EMC_ZQ_CAL 0x2ec +#define EMC_XM2CMDPADCTRL 0x2f0 +#define EMC_XM2DQSPADCTRL2 0x2fc +#define EMC_XM2DQPADCTRL2 0x304 +#define EMC_XM2CLKPADCTRL 0x308 +#define EMC_XM2COMPPADCTRL 0x30c +#define EMC_XM2VTTGENPADCTRL 0x310 +#define EMC_XM2VTTGENPADCTRL2 0x314 +#define EMC_XM2QUSEPADCTRL 0x318 +#define EMC_DLL_XFORM_DQS0 0x328 +#define EMC_DLL_XFORM_DQS1 0x32c +#define EMC_DLL_XFORM_DQS2 0x330 +#define EMC_DLL_XFORM_DQS3 0x334 +#define EMC_DLL_XFORM_DQS4 0x338 +#define EMC_DLL_XFORM_DQS5 0x33c +#define EMC_DLL_XFORM_DQS6 0x340 +#define EMC_DLL_XFORM_DQS7 0x344 +#define EMC_DLL_XFORM_QUSE0 0x348 +#define EMC_DLL_XFORM_QUSE1 0x34c +#define EMC_DLL_XFORM_QUSE2 0x350 +#define EMC_DLL_XFORM_QUSE3 0x354 +#define EMC_DLL_XFORM_QUSE4 0x358 +#define EMC_DLL_XFORM_QUSE5 0x35c +#define EMC_DLL_XFORM_QUSE6 0x360 +#define EMC_DLL_XFORM_QUSE7 0x364 +#define EMC_DLL_XFORM_DQ0 0x368 +#define EMC_DLL_XFORM_DQ1 0x36c +#define EMC_DLL_XFORM_DQ2 0x370 +#define EMC_DLL_XFORM_DQ3 0x374 +#define EMC_DLI_TRIM_TXDQS0 0x3a8 +#define EMC_DLI_TRIM_TXDQS1 0x3ac +#define EMC_DLI_TRIM_TXDQS2 0x3b0 +#define EMC_DLI_TRIM_TXDQS3 0x3b4 +#define EMC_DLI_TRIM_TXDQS4 0x3b8 +#define EMC_DLI_TRIM_TXDQS5 0x3bc +#define EMC_DLI_TRIM_TXDQS6 0x3c0 +#define EMC_DLI_TRIM_TXDQS7 0x3c4 +#define EMC_STALL_THEN_EXE_BEFORE_CLKCHANGE 0x3c8 +#define EMC_STALL_THEN_EXE_AFTER_CLKCHANGE 0x3cc +#define EMC_UNSTALL_RW_AFTER_CLKCHANGE 0x3d0 +#define EMC_SEL_DPD_CTRL 0x3d8 +#define EMC_PRE_REFRESH_REQ_CNT 0x3dc +#define EMC_DYN_SELF_REF_CONTROL 0x3e0 +#define EMC_TXSRDLL 0x3e4 + +#define EMC_STATUS_TIMING_UPDATE_STALLED BIT(23) + +#define EMC_MODE_SET_DLL_RESET BIT(8) +#define EMC_MODE_SET_LONG_CNT BIT(26) + +#define EMC_SELF_REF_CMD_ENABLED BIT(0) + +#define DRAM_DEV_SEL_ALL (0 << 30) +#define DRAM_DEV_SEL_0 (2 << 30) +#define DRAM_DEV_SEL_1 (1 << 30) +#define DRAM_BROADCAST(num) \ + ((num) > 1 ? DRAM_DEV_SEL_ALL : DRAM_DEV_SEL_0) + +#define EMC_ZQ_CAL_CMD BIT(0) +#define EMC_ZQ_CAL_LONG BIT(4) +#define EMC_ZQ_CAL_LONG_CMD_DEV0 \ + (DRAM_DEV_SEL_0 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD) +#define EMC_ZQ_CAL_LONG_CMD_DEV1 \ + (DRAM_DEV_SEL_1 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD) + +#define EMC_DBG_READ_MUX_ASSEMBLY BIT(0) +#define EMC_DBG_WRITE_MUX_ACTIVE BIT(1) +#define EMC_DBG_FORCE_UPDATE BIT(2) +#define EMC_DBG_CFG_PRIORITY BIT(24) + +#define EMC_CFG5_QUSE_MODE_SHIFT 13 +#define EMC_CFG5_QUSE_MODE_MASK (7 << EMC_CFG5_QUSE_MODE_SHIFT) + +#define EMC_CFG5_QUSE_MODE_INTERNAL_LPBK 2 +#define EMC_CFG5_QUSE_MODE_PULSE_INTERN 3 + +#define EMC_SEL_DPD_CTRL_QUSE_DPD_ENABLE BIT(9) + +#define EMC_XM2COMPPADCTRL_VREF_CAL_ENABLE BIT(10) + +#define EMC_XM2QUSEPADCTRL_IVREF_ENABLE BIT(4) + +#define EMC_XM2DQSPADCTRL2_VREF_ENABLE BIT(5) +#define EMC_XM2DQSPADCTRL3_VREF_ENABLE BIT(5) + +#define EMC_AUTO_CAL_STATUS_ACTIVE BIT(31) + +#define EMC_FBIO_CFG5_DRAM_TYPE_MASK 0x3 + +#define EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK 0x3ff +#define EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT 16 +#define EMC_MRS_WAIT_CNT_LONG_WAIT_MASK \ + (0x3ff << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) + +#define EMC_REFCTRL_DEV_SEL_MASK 0x3 +#define EMC_REFCTRL_ENABLE BIT(31) +#define EMC_REFCTRL_ENABLE_ALL(num) \ + (((num) > 1 ? 0 : 2) | EMC_REFCTRL_ENABLE) +#define EMC_REFCTRL_DISABLE_ALL(num) ((num) > 1 ? 0 : 2) + +#define EMC_CFG_PERIODIC_QRST BIT(21) +#define EMC_CFG_DYN_SREF_ENABLE BIT(28) + +#define EMC_CLKCHANGE_REQ_ENABLE BIT(0) +#define EMC_CLKCHANGE_PD_ENABLE BIT(1) +#define EMC_CLKCHANGE_SR_ENABLE BIT(2) + +#define EMC_TIMING_UPDATE BIT(0) + +#define EMC_REFRESH_OVERFLOW_INT BIT(3) +#define EMC_CLKCHANGE_COMPLETE_INT BIT(4) + +enum emc_dram_type { + DRAM_TYPE_DDR3, + DRAM_TYPE_DDR1, + DRAM_TYPE_LPDDR2, + DRAM_TYPE_DDR2, +}; + +enum emc_dll_change { + DLL_CHANGE_NONE, + DLL_CHANGE_ON, + DLL_CHANGE_OFF +}; + +static const u16 emc_timing_registers[] = { + [0] = EMC_RC, + [1] = EMC_RFC, + [2] = EMC_RAS, + [3] = EMC_RP, + [4] = EMC_R2W, + [5] = EMC_W2R, + [6] = EMC_R2P, + [7] = EMC_W2P, + [8] = EMC_RD_RCD, + [9] = EMC_WR_RCD, + [10] = EMC_RRD, + [11] = EMC_REXT, + [12] = EMC_WEXT, + [13] = EMC_WDV, + [14] = EMC_QUSE, + [15] = EMC_QRST, + [16] = EMC_QSAFE, + [17] = EMC_RDV, + [18] = EMC_REFRESH, + [19] = EMC_BURST_REFRESH_NUM, + [20] = EMC_PRE_REFRESH_REQ_CNT, + [21] = EMC_PDEX2WR, + [22] = EMC_PDEX2RD, + [23] = EMC_PCHG2PDEN, + [24] = EMC_ACT2PDEN, + [25] = EMC_AR2PDEN, + [26] = EMC_RW2PDEN, + [27] = EMC_TXSR, + [28] = EMC_TXSRDLL, + [29] = EMC_TCKE, + [30] = EMC_TFAW, + [31] = EMC_TRPAB, + [32] = EMC_TCLKSTABLE, + [33] = EMC_TCLKSTOP, + [34] = EMC_TREFBW, + [35] = EMC_QUSE_EXTRA, + [36] = EMC_FBIO_CFG6, + [37] = EMC_ODT_WRITE, + [38] = EMC_ODT_READ, + [39] = EMC_FBIO_CFG5, + [40] = EMC_CFG_DIG_DLL, + [41] = EMC_CFG_DIG_DLL_PERIOD, + [42] = EMC_DLL_XFORM_DQS0, + [43] = EMC_DLL_XFORM_DQS1, + [44] = EMC_DLL_XFORM_DQS2, + [45] = EMC_DLL_XFORM_DQS3, + [46] = EMC_DLL_XFORM_DQS4, + [47] = EMC_DLL_XFORM_DQS5, + [48] = EMC_DLL_XFORM_DQS6, + [49] = EMC_DLL_XFORM_DQS7, + [50] = EMC_DLL_XFORM_QUSE0, + [51] = EMC_DLL_XFORM_QUSE1, + [52] = EMC_DLL_XFORM_QUSE2, + [53] = EMC_DLL_XFORM_QUSE3, + [54] = EMC_DLL_XFORM_QUSE4, + [55] = EMC_DLL_XFORM_QUSE5, + [56] = EMC_DLL_XFORM_QUSE6, + [57] = EMC_DLL_XFORM_QUSE7, + [58] = EMC_DLI_TRIM_TXDQS0, + [59] = EMC_DLI_TRIM_TXDQS1, + [60] = EMC_DLI_TRIM_TXDQS2, + [61] = EMC_DLI_TRIM_TXDQS3, + [62] = EMC_DLI_TRIM_TXDQS4, + [63] = EMC_DLI_TRIM_TXDQS5, + [64] = EMC_DLI_TRIM_TXDQS6, + [65] = EMC_DLI_TRIM_TXDQS7, + [66] = EMC_DLL_XFORM_DQ0, + [67] = EMC_DLL_XFORM_DQ1, + [68] = EMC_DLL_XFORM_DQ2, + [69] = EMC_DLL_XFORM_DQ3, + [70] = EMC_XM2CMDPADCTRL, + [71] = EMC_XM2DQSPADCTRL2, + [72] = EMC_XM2DQPADCTRL2, + [73] = EMC_XM2CLKPADCTRL, + [74] = EMC_XM2COMPPADCTRL, + [75] = EMC_XM2VTTGENPADCTRL, + [76] = EMC_XM2VTTGENPADCTRL2, + [77] = EMC_XM2QUSEPADCTRL, + [78] = EMC_XM2DQSPADCTRL3, + [79] = EMC_CTT_TERM_CTRL, + [80] = EMC_ZCAL_INTERVAL, + [81] = EMC_ZCAL_WAIT_CNT, + [82] = EMC_MRS_WAIT_CNT, + [83] = EMC_AUTO_CAL_CONFIG, + [84] = EMC_CTT, + [85] = EMC_CTT_DURATION, + [86] = EMC_DYN_SELF_REF_CONTROL, + [87] = EMC_FBIO_SPARE, + [88] = EMC_CFG_RSV, +}; + +struct emc_timing { + unsigned long rate; + + u32 data[ARRAY_SIZE(emc_timing_registers)]; + + u32 emc_auto_cal_interval; + u32 emc_mode_1; + u32 emc_mode_2; + u32 emc_mode_reset; + u32 emc_zcal_cnt_long; + bool emc_cfg_periodic_qrst; + bool emc_cfg_dyn_self_ref; +}; + +struct tegra_emc { + struct device *dev; + struct tegra_mc *mc; + struct completion clk_handshake_complete; + struct notifier_block clk_nb; + struct clk *clk; + void __iomem *regs; + unsigned int irq; + + struct emc_timing *timings; + unsigned int num_timings; + + u32 mc_override; + u32 emc_cfg; + + u32 emc_mode_1; + u32 emc_mode_2; + u32 emc_mode_reset; + + bool vref_cal_toggle : 1; + bool zcal_long : 1; + bool dll_on : 1; + bool prepared : 1; + bool bad_state : 1; +}; + +static irqreturn_t tegra_emc_isr(int irq, void *data) +{ + struct tegra_emc *emc = data; + u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT; + u32 status; + + status = readl_relaxed(emc->regs + EMC_INTSTATUS) & intmask; + if (!status) + return IRQ_NONE; + + /* notify about EMC-CAR handshake completion */ + if (status & EMC_CLKCHANGE_COMPLETE_INT) + complete(&emc->clk_handshake_complete); + + /* notify about HW problem */ + if (status & EMC_REFRESH_OVERFLOW_INT) + dev_err_ratelimited(emc->dev, + "refresh request overflow timeout\n"); + + /* clear interrupts */ + writel_relaxed(status, emc->regs + EMC_INTSTATUS); + + return IRQ_HANDLED; +} + +static struct emc_timing *emc_find_timing(struct tegra_emc *emc, + unsigned long rate) +{ + struct emc_timing *timing = NULL; + unsigned int i; + + for (i = 0; i < emc->num_timings; i++) { + if (emc->timings[i].rate >= rate) { + timing = &emc->timings[i]; + break; + } + } + + if (!timing) { + dev_err(emc->dev, "no timing for rate %lu\n", rate); + return NULL; + } + + return timing; +} + +static bool emc_dqs_preset(struct tegra_emc *emc, struct emc_timing *timing, + bool *schmitt_to_vref) +{ + bool preset = false; + u32 val; + + if (timing->data[71] & EMC_XM2DQSPADCTRL2_VREF_ENABLE) { + val = readl_relaxed(emc->regs + EMC_XM2DQSPADCTRL2); + + if (!(val & EMC_XM2DQSPADCTRL2_VREF_ENABLE)) { + val |= EMC_XM2DQSPADCTRL2_VREF_ENABLE; + writel_relaxed(val, emc->regs + EMC_XM2DQSPADCTRL2); + + preset = true; + } + } + + if (timing->data[78] & EMC_XM2DQSPADCTRL3_VREF_ENABLE) { + val = readl_relaxed(emc->regs + EMC_XM2DQSPADCTRL3); + + if (!(val & EMC_XM2DQSPADCTRL3_VREF_ENABLE)) { + val |= EMC_XM2DQSPADCTRL3_VREF_ENABLE; + writel_relaxed(val, emc->regs + EMC_XM2DQSPADCTRL3); + + preset = true; + } + } + + if (timing->data[77] & EMC_XM2QUSEPADCTRL_IVREF_ENABLE) { + val = readl_relaxed(emc->regs + EMC_XM2QUSEPADCTRL); + + if (!(val & EMC_XM2QUSEPADCTRL_IVREF_ENABLE)) { + val |= EMC_XM2QUSEPADCTRL_IVREF_ENABLE; + writel_relaxed(val, emc->regs + EMC_XM2QUSEPADCTRL); + + *schmitt_to_vref = true; + preset = true; + } + } + + return preset; +} + +static int emc_seq_update_timing(struct tegra_emc *emc) +{ + u32 val; + int err; + + writel_relaxed(EMC_TIMING_UPDATE, emc->regs + EMC_TIMING_CONTROL); + + err = readl_relaxed_poll_timeout_atomic(emc->regs + EMC_STATUS, val, + !(val & EMC_STATUS_TIMING_UPDATE_STALLED), + 1, 200); + if (err) { + dev_err(emc->dev, "failed to update timing: %d\n", err); + return err; + } + + return 0; +} + +static int emc_prepare_mc_clk_cfg(struct tegra_emc *emc, unsigned long rate) +{ + struct tegra_mc *mc = emc->mc; + unsigned int misc0_index = 16; + unsigned int i; + bool same; + + for (i = 0; i < mc->num_timings; i++) { + if (mc->timings[i].rate != rate) + continue; + + if (mc->timings[i].emem_data[misc0_index] & BIT(27)) + same = true; + else + same = false; + + return tegra20_clk_prepare_emc_mc_same_freq(emc->clk, same); + } + + return -EINVAL; +} + +static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate) +{ + struct emc_timing *timing = emc_find_timing(emc, rate); + enum emc_dll_change dll_change; + enum emc_dram_type dram_type; + bool schmitt_to_vref = false; + unsigned int pre_wait = 0; + bool qrst_used = false; + unsigned int dram_num; + unsigned int i; + u32 fbio_cfg5; + u32 emc_dbg; + u32 val; + int err; + + if (!timing || emc->bad_state) + return -EINVAL; + + dev_dbg(emc->dev, "%s: using timing rate %lu for requested rate %lu\n", + __func__, timing->rate, rate); + + emc->bad_state = true; + + err = emc_prepare_mc_clk_cfg(emc, rate); + if (err) { + dev_err(emc->dev, "mc clock preparation failed: %d\n", err); + return err; + } + + emc->vref_cal_toggle = false; + emc->mc_override = mc_readl(emc->mc, MC_EMEM_ARB_OVERRIDE); + emc->emc_cfg = readl_relaxed(emc->regs + EMC_CFG); + emc_dbg = readl_relaxed(emc->regs + EMC_DBG); + + if (emc->dll_on == !!(timing->emc_mode_1 & 0x1)) + dll_change = DLL_CHANGE_NONE; + else if (timing->emc_mode_1 & 0x1) + dll_change = DLL_CHANGE_ON; + else + dll_change = DLL_CHANGE_OFF; + + emc->dll_on = !!(timing->emc_mode_1 & 0x1); + + if (timing->data[80] && !readl_relaxed(emc->regs + EMC_ZCAL_INTERVAL)) + emc->zcal_long = true; + else + emc->zcal_long = false; + + fbio_cfg5 = readl_relaxed(emc->regs + EMC_FBIO_CFG5); + dram_type = fbio_cfg5 & EMC_FBIO_CFG5_DRAM_TYPE_MASK; + + dram_num = tegra_mc_get_emem_device_count(emc->mc); + + /* disable dynamic self-refresh */ + if (emc->emc_cfg & EMC_CFG_DYN_SREF_ENABLE) { + emc->emc_cfg &= ~EMC_CFG_DYN_SREF_ENABLE; + writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG); + + pre_wait = 5; + } + + /* update MC arbiter settings */ + val = mc_readl(emc->mc, MC_EMEM_ARB_OUTSTANDING_REQ); + if (!(val & MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE) || + ((val & MC_EMEM_ARB_OUTSTANDING_REQ_MAX_MASK) > 0x50)) { + + val = MC_EMEM_ARB_OUTSTANDING_REQ_LIMIT_ENABLE | + MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE | 0x50; + mc_writel(emc->mc, val, MC_EMEM_ARB_OUTSTANDING_REQ); + mc_writel(emc->mc, MC_TIMING_UPDATE, MC_TIMING_CONTROL); + } + + if (emc->mc_override & MC_EMEM_ARB_OVERRIDE_EACK_MASK) + mc_writel(emc->mc, + emc->mc_override & ~MC_EMEM_ARB_OVERRIDE_EACK_MASK, + MC_EMEM_ARB_OVERRIDE); + + /* check DQ/DQS VREF delay */ + if (emc_dqs_preset(emc, timing, &schmitt_to_vref)) { + if (pre_wait < 3) + pre_wait = 3; + } + + if (pre_wait) { + err = emc_seq_update_timing(emc); + if (err) + return err; + + udelay(pre_wait); + } + + /* disable auto-calibration if VREF mode is switching */ + if (timing->emc_auto_cal_interval) { + val = readl_relaxed(emc->regs + EMC_XM2COMPPADCTRL); + val ^= timing->data[74]; + + if (val & EMC_XM2COMPPADCTRL_VREF_CAL_ENABLE) { + writel_relaxed(0, emc->regs + EMC_AUTO_CAL_INTERVAL); + + err = readl_relaxed_poll_timeout_atomic( + emc->regs + EMC_AUTO_CAL_STATUS, val, + !(val & EMC_AUTO_CAL_STATUS_ACTIVE), 1, 300); + if (err) { + dev_err(emc->dev, + "failed to disable auto-cal: %d\n", + err); + return err; + } + + emc->vref_cal_toggle = true; + } + } + + /* program shadow registers */ + for (i = 0; i < ARRAY_SIZE(timing->data); i++) { + /* EMC_XM2CLKPADCTRL should be programmed separately */ + if (i != 73) + writel_relaxed(timing->data[i], + emc->regs + emc_timing_registers[i]); + } + + err = tegra_mc_write_emem_configuration(emc->mc, timing->rate); + if (err) + return err; + + /* DDR3: predict MRS long wait count */ + if (dram_type == DRAM_TYPE_DDR3 && dll_change == DLL_CHANGE_ON) { + u32 cnt = 512; + + if (emc->zcal_long) + cnt -= dram_num * 256; + + val = timing->data[82] & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK; + if (cnt < val) + cnt = val; + + val = timing->data[82] & ~EMC_MRS_WAIT_CNT_LONG_WAIT_MASK; + val |= (cnt << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) & + EMC_MRS_WAIT_CNT_LONG_WAIT_MASK; + + writel_relaxed(val, emc->regs + EMC_MRS_WAIT_CNT); + } + + /* disable interrupt since read access is prohibited after stalling */ + disable_irq(emc->irq); + + /* this read also completes the writes */ + val = readl_relaxed(emc->regs + EMC_SEL_DPD_CTRL); + + if (!(val & EMC_SEL_DPD_CTRL_QUSE_DPD_ENABLE) && schmitt_to_vref) { + u32 cur_mode, new_mode; + + cur_mode = fbio_cfg5 & EMC_CFG5_QUSE_MODE_MASK; + cur_mode >>= EMC_CFG5_QUSE_MODE_SHIFT; + + new_mode = timing->data[39] & EMC_CFG5_QUSE_MODE_MASK; + new_mode >>= EMC_CFG5_QUSE_MODE_SHIFT; + + if ((cur_mode != EMC_CFG5_QUSE_MODE_PULSE_INTERN && + cur_mode != EMC_CFG5_QUSE_MODE_INTERNAL_LPBK) || + (new_mode != EMC_CFG5_QUSE_MODE_PULSE_INTERN && + new_mode != EMC_CFG5_QUSE_MODE_INTERNAL_LPBK)) + qrst_used = true; + } + + /* flow control marker 1 */ + writel_relaxed(0x1, emc->regs + EMC_STALL_THEN_EXE_BEFORE_CLKCHANGE); + + /* enable periodic reset */ + if (qrst_used) { + writel_relaxed(emc_dbg | EMC_DBG_WRITE_MUX_ACTIVE, + emc->regs + EMC_DBG); + writel_relaxed(emc->emc_cfg | EMC_CFG_PERIODIC_QRST, + emc->regs + EMC_CFG); + writel_relaxed(emc_dbg, emc->regs + EMC_DBG); + } + + /* disable auto-refresh to save time after clock change */ + writel_relaxed(EMC_REFCTRL_DISABLE_ALL(dram_num), + emc->regs + EMC_REFCTRL); + + /* turn off DLL and enter self-refresh on DDR3 */ + if (dram_type == DRAM_TYPE_DDR3) { + if (dll_change == DLL_CHANGE_OFF) + writel_relaxed(timing->emc_mode_1, + emc->regs + EMC_EMRS); + + writel_relaxed(DRAM_BROADCAST(dram_num) | + EMC_SELF_REF_CMD_ENABLED, + emc->regs + EMC_SELF_REF); + } + + /* flow control marker 2 */ + writel_relaxed(0x1, emc->regs + EMC_STALL_THEN_EXE_AFTER_CLKCHANGE); + + /* enable write-active MUX, update unshadowed pad control */ + writel_relaxed(emc_dbg | EMC_DBG_WRITE_MUX_ACTIVE, emc->regs + EMC_DBG); + writel_relaxed(timing->data[73], emc->regs + EMC_XM2CLKPADCTRL); + + /* restore periodic QRST and disable write-active MUX */ + val = !!(emc->emc_cfg & EMC_CFG_PERIODIC_QRST); + if (qrst_used || timing->emc_cfg_periodic_qrst != val) { + if (timing->emc_cfg_periodic_qrst) + emc->emc_cfg |= EMC_CFG_PERIODIC_QRST; + else + emc->emc_cfg &= ~EMC_CFG_PERIODIC_QRST; + + writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG); + } + writel_relaxed(emc_dbg, emc->regs + EMC_DBG); + + /* exit self-refresh on DDR3 */ + if (dram_type == DRAM_TYPE_DDR3) + writel_relaxed(DRAM_BROADCAST(dram_num), + emc->regs + EMC_SELF_REF); + + /* set DRAM-mode registers */ + if (dram_type == DRAM_TYPE_DDR3) { + if (timing->emc_mode_1 != emc->emc_mode_1) + writel_relaxed(timing->emc_mode_1, + emc->regs + EMC_EMRS); + + if (timing->emc_mode_2 != emc->emc_mode_2) + writel_relaxed(timing->emc_mode_2, + emc->regs + EMC_EMRS); + + if (timing->emc_mode_reset != emc->emc_mode_reset || + dll_change == DLL_CHANGE_ON) { + val = timing->emc_mode_reset; + if (dll_change == DLL_CHANGE_ON) { + val |= EMC_MODE_SET_DLL_RESET; + val |= EMC_MODE_SET_LONG_CNT; + } else { + val &= ~EMC_MODE_SET_DLL_RESET; + } + writel_relaxed(val, emc->regs + EMC_MRS); + } + } else { + if (timing->emc_mode_2 != emc->emc_mode_2) + writel_relaxed(timing->emc_mode_2, + emc->regs + EMC_MRW); + + if (timing->emc_mode_1 != emc->emc_mode_1) + writel_relaxed(timing->emc_mode_1, + emc->regs + EMC_MRW); + } + + emc->emc_mode_1 = timing->emc_mode_1; + emc->emc_mode_2 = timing->emc_mode_2; + emc->emc_mode_reset = timing->emc_mode_reset; + + /* issue ZCAL command if turning ZCAL on */ + if (emc->zcal_long) { + writel_relaxed(EMC_ZQ_CAL_LONG_CMD_DEV0, + emc->regs + EMC_ZQ_CAL); + + if (dram_num > 1) + writel_relaxed(EMC_ZQ_CAL_LONG_CMD_DEV1, + emc->regs + EMC_ZQ_CAL); + } + + /* re-enable auto-refresh */ + writel_relaxed(EMC_REFCTRL_ENABLE_ALL(dram_num), + emc->regs + EMC_REFCTRL); + + /* flow control marker 3 */ + writel_relaxed(0x1, emc->regs + EMC_UNSTALL_RW_AFTER_CLKCHANGE); + + reinit_completion(&emc->clk_handshake_complete); + + /* interrupt can be re-enabled now */ + enable_irq(emc->irq); + + emc->bad_state = false; + emc->prepared = true; + + return 0; +} + +static int emc_complete_timing_change(struct tegra_emc *emc, + unsigned long rate) +{ + struct emc_timing *timing = emc_find_timing(emc, rate); + unsigned long timeout; + int ret; + + timeout = wait_for_completion_timeout(&emc->clk_handshake_complete, + msecs_to_jiffies(100)); + if (timeout == 0) { + dev_err(emc->dev, "emc-car handshake failed\n"); + emc->bad_state = true; + return -EIO; + } + + /* restore auto-calibration */ + if (emc->vref_cal_toggle) + writel_relaxed(timing->emc_auto_cal_interval, + emc->regs + EMC_AUTO_CAL_INTERVAL); + + /* restore dynamic self-refresh */ + if (timing->emc_cfg_dyn_self_ref) { + emc->emc_cfg |= EMC_CFG_DYN_SREF_ENABLE; + writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG); + } + + /* set number of clocks to wait after each ZQ command */ + if (emc->zcal_long) + writel_relaxed(timing->emc_zcal_cnt_long, + emc->regs + EMC_ZCAL_WAIT_CNT); + + udelay(2); + /* update restored timing */ + ret = emc_seq_update_timing(emc); + if (ret) + emc->bad_state = true; + + /* restore early ACK */ + mc_writel(emc->mc, emc->mc_override, MC_EMEM_ARB_OVERRIDE); + + emc->prepared = false; + + return ret; +} + +static int emc_unprepare_timing_change(struct tegra_emc *emc, + unsigned long rate) +{ + if (emc->prepared && !emc->bad_state) { + /* shouldn't ever happen in practice */ + dev_err(emc->dev, "timing configuration can't be reverted\n"); + emc->bad_state = true; + } + + return 0; +} + +static int emc_clk_change_notify(struct notifier_block *nb, + unsigned long msg, void *data) +{ + struct tegra_emc *emc = container_of(nb, struct tegra_emc, clk_nb); + struct clk_notifier_data *cnd = data; + int err; + + switch (msg) { + case PRE_RATE_CHANGE: + err = emc_prepare_timing_change(emc, cnd->new_rate); + break; + + case ABORT_RATE_CHANGE: + err = emc_unprepare_timing_change(emc, cnd->old_rate); + break; + + case POST_RATE_CHANGE: + err = emc_complete_timing_change(emc, cnd->new_rate); + break; + + default: + return NOTIFY_DONE; + } + + return notifier_from_errno(err); +} + +static int load_one_timing_from_dt(struct tegra_emc *emc, + struct emc_timing *timing, + struct device_node *node) +{ + u32 value; + int err; + + err = of_property_read_u32(node, "clock-frequency", &value); + if (err) { + dev_err(emc->dev, "timing %pOF: failed to read rate: %d\n", + node, err); + return err; + } + + timing->rate = value; + + err = of_property_read_u32_array(node, "nvidia,emc-configuration", + timing->data, + ARRAY_SIZE(emc_timing_registers)); + if (err) { + dev_err(emc->dev, + "timing %pOF: failed to read emc timing data: %d\n", + node, err); + return err; + } + +#define EMC_READ_BOOL(prop, dtprop) \ + timing->prop = of_property_read_bool(node, dtprop); + +#define EMC_READ_U32(prop, dtprop) \ + err = of_property_read_u32(node, dtprop, &timing->prop); \ + if (err) { \ + dev_err(emc->dev, \ + "timing %pOFn: failed to read " #prop ": %d\n", \ + node, err); \ + return err; \ + } + + EMC_READ_U32(emc_auto_cal_interval, "nvidia,emc-auto-cal-interval") + EMC_READ_U32(emc_mode_1, "nvidia,emc-mode-1") + EMC_READ_U32(emc_mode_2, "nvidia,emc-mode-2") + EMC_READ_U32(emc_mode_reset, "nvidia,emc-mode-reset") + EMC_READ_U32(emc_zcal_cnt_long, "nvidia,emc-zcal-cnt-long") + EMC_READ_BOOL(emc_cfg_dyn_self_ref, "nvidia,emc-cfg-dyn-self-ref") + EMC_READ_BOOL(emc_cfg_periodic_qrst, "nvidia,emc-cfg-periodic-qrst") + +#undef EMC_READ_U32 +#undef EMC_READ_BOOL + + dev_dbg(emc->dev, "%s: %pOF: rate %lu\n", __func__, node, timing->rate); + + return 0; +} + +static int cmp_timings(const void *_a, const void *_b) +{ + const struct emc_timing *a = _a; + const struct emc_timing *b = _b; + + if (a->rate < b->rate) + return -1; + + if (a->rate > b->rate) + return 1; + + return 0; +} + +static int emc_check_mc_timings(struct tegra_emc *emc) +{ + struct tegra_mc *mc = emc->mc; + unsigned int i; + + if (emc->num_timings != mc->num_timings) { + dev_err(emc->dev, "emc/mc timings number mismatch: %u %u\n", + emc->num_timings, mc->num_timings); + return -EINVAL; + } + + for (i = 0; i < mc->num_timings; i++) { + if (emc->timings[i].rate != mc->timings[i].rate) { + dev_err(emc->dev, + "emc/mc timing rate mismatch: %lu %lu\n", + emc->timings[i].rate, mc->timings[i].rate); + return -EINVAL; + } + } + + return 0; +} + +static int emc_load_timings_from_dt(struct tegra_emc *emc, + struct device_node *node) +{ + struct device_node *child; + struct emc_timing *timing; + int child_count; + int err; + + child_count = of_get_child_count(node); + if (!child_count) { + dev_err(emc->dev, "no memory timings in: %pOF\n", node); + return -EINVAL; + } + + emc->timings = devm_kcalloc(emc->dev, child_count, sizeof(*timing), + GFP_KERNEL); + if (!emc->timings) + return -ENOMEM; + + emc->num_timings = child_count; + timing = emc->timings; + + for_each_child_of_node(node, child) { + err = load_one_timing_from_dt(emc, timing++, child); + if (err) { + of_node_put(child); + return err; + } + } + + sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings, + NULL); + + err = emc_check_mc_timings(emc); + if (err) + return err; + + dev_info(emc->dev, + "got %u timings for RAM code %u (min %luMHz max %luMHz)\n", + emc->num_timings, + tegra_read_ram_code(), + emc->timings[0].rate / 1000000, + emc->timings[emc->num_timings - 1].rate / 1000000); + + return 0; +} + +static struct device_node *emc_find_node_by_ram_code(struct device *dev) +{ + struct device_node *np; + u32 value, ram_code; + int err; + + ram_code = tegra_read_ram_code(); + + for_each_child_of_node(dev->of_node, np) { + err = of_property_read_u32(np, "nvidia,ram-code", &value); + if (err || value != ram_code) + continue; + + return np; + } + + dev_err(dev, "no memory timings for RAM code %u found in device-tree\n", + ram_code); + + return NULL; +} + +static int emc_setup_hw(struct tegra_emc *emc) +{ + u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT; + u32 fbio_cfg5, emc_cfg, emc_dbg; + enum emc_dram_type dram_type; + + fbio_cfg5 = readl_relaxed(emc->regs + EMC_FBIO_CFG5); + dram_type = fbio_cfg5 & EMC_FBIO_CFG5_DRAM_TYPE_MASK; + + emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2); + + /* enable EMC and CAR to handshake on PLL divider/source changes */ + emc_cfg |= EMC_CLKCHANGE_REQ_ENABLE; + + /* configure clock change mode accordingly to DRAM type */ + switch (dram_type) { + case DRAM_TYPE_LPDDR2: + emc_cfg |= EMC_CLKCHANGE_PD_ENABLE; + emc_cfg &= ~EMC_CLKCHANGE_SR_ENABLE; + break; + + default: + emc_cfg &= ~EMC_CLKCHANGE_SR_ENABLE; + emc_cfg &= ~EMC_CLKCHANGE_PD_ENABLE; + break; + } + + writel_relaxed(emc_cfg, emc->regs + EMC_CFG_2); + + /* initialize interrupt */ + writel_relaxed(intmask, emc->regs + EMC_INTMASK); + writel_relaxed(0xffffffff, emc->regs + EMC_INTSTATUS); + + /* ensure that unwanted debug features are disabled */ + emc_dbg = readl_relaxed(emc->regs + EMC_DBG); + emc_dbg |= EMC_DBG_CFG_PRIORITY; + emc_dbg &= ~EMC_DBG_READ_MUX_ASSEMBLY; + emc_dbg &= ~EMC_DBG_WRITE_MUX_ACTIVE; + emc_dbg &= ~EMC_DBG_FORCE_UPDATE; + writel_relaxed(emc_dbg, emc->regs + EMC_DBG); + + return 0; +} + +static long emc_round_rate(unsigned long rate, + unsigned long min_rate, + unsigned long max_rate, + void *arg) +{ + struct emc_timing *timing = NULL; + struct tegra_emc *emc = arg; + unsigned int i; + + min_rate = min(min_rate, emc->timings[emc->num_timings - 1].rate); + + for (i = 0; i < emc->num_timings; i++) { + if (emc->timings[i].rate < rate && i != emc->num_timings - 1) + continue; + + if (emc->timings[i].rate > max_rate) { + i = max(i, 1u) - 1; + + if (emc->timings[i].rate < min_rate) + break; + } + + if (emc->timings[i].rate < min_rate) + continue; + + timing = &emc->timings[i]; + break; + } + + if (!timing) { + dev_err(emc->dev, "no timing for rate %lu min %lu max %lu\n", + rate, min_rate, max_rate); + return -EINVAL; + } + + return timing->rate; +} + +static int tegra_emc_probe(struct platform_device *pdev) +{ + struct platform_device *mc; + struct device_node *np; + struct tegra_emc *emc; + int err; + + if (of_get_child_count(pdev->dev.of_node) == 0) { + dev_info(&pdev->dev, + "device-tree node doesn't have memory timings\n"); + return 0; + } + + np = of_parse_phandle(pdev->dev.of_node, "nvidia,memory-controller", 0); + if (!np) { + dev_err(&pdev->dev, "could not get memory controller node\n"); + return -ENOENT; + } + + mc = of_find_device_by_node(np); + of_node_put(np); + if (!mc) + return -ENOENT; + + np = emc_find_node_by_ram_code(&pdev->dev); + if (!np) + return -EINVAL; + + emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL); + if (!emc) { + of_node_put(np); + return -ENOMEM; + } + + emc->mc = platform_get_drvdata(mc); + if (!emc->mc) + return -EPROBE_DEFER; + + init_completion(&emc->clk_handshake_complete); + emc->clk_nb.notifier_call = emc_clk_change_notify; + emc->dev = &pdev->dev; + + err = emc_load_timings_from_dt(emc, np); + of_node_put(np); + if (err) + return err; + + emc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(emc->regs)) + return PTR_ERR(emc->regs); + + err = emc_setup_hw(emc); + if (err) + return err; + + err = platform_get_irq(pdev, 0); + if (err < 0) { + dev_err(&pdev->dev, "interrupt not specified: %d\n", err); + return err; + } + emc->irq = err; + + err = devm_request_irq(&pdev->dev, emc->irq, tegra_emc_isr, 0, + dev_name(&pdev->dev), emc); + if (err) { + dev_err(&pdev->dev, "failed to request irq: %d\n", err); + return err; + } + + tegra20_clk_set_emc_round_callback(emc_round_rate, emc); + + emc->clk = devm_clk_get(&pdev->dev, "emc"); + if (IS_ERR(emc->clk)) { + err = PTR_ERR(emc->clk); + dev_err(&pdev->dev, "failed to get emc clock: %d\n", err); + goto unset_cb; + } + + err = clk_notifier_register(emc->clk, &emc->clk_nb); + if (err) { + dev_err(&pdev->dev, "failed to register clk notifier: %d\n", + err); + goto unset_cb; + } + + platform_set_drvdata(pdev, emc); + + return 0; + +unset_cb: + tegra20_clk_set_emc_round_callback(NULL, NULL); + + return err; +} + +static int tegra_emc_suspend(struct device *dev) +{ + struct tegra_emc *emc = dev_get_drvdata(dev); + + /* + * Suspending in a bad state will hang machine. The "prepared" var + * shall be always false here unless it's a kernel bug that caused + * suspending in a wrong order. + */ + if (WARN_ON(emc->prepared) || emc->bad_state) + return -EINVAL; + + emc->bad_state = true; + + return 0; +} + +static int tegra_emc_resume(struct device *dev) +{ + struct tegra_emc *emc = dev_get_drvdata(dev); + + emc_setup_hw(emc); + emc->bad_state = false; + + return 0; +} + +static const struct dev_pm_ops tegra_emc_pm_ops = { + .suspend = tegra_emc_suspend, + .resume = tegra_emc_resume, +}; + +static const struct of_device_id tegra_emc_of_match[] = { + { .compatible = "nvidia,tegra30-emc", }, + {}, +}; + +static struct platform_driver tegra_emc_driver = { + .probe = tegra_emc_probe, + .driver = { + .name = "tegra30-emc", + .of_match_table = tegra_emc_of_match, + .pm = &tegra_emc_pm_ops, + .suppress_bind_attrs = true, + }, +}; + +static int __init tegra_emc_init(void) +{ + return platform_driver_register(&tegra_emc_driver); +} +subsys_initcall(tegra_emc_init); diff --git a/drivers/memory/tegra/tegra30.c b/drivers/memory/tegra/tegra30.c index 8947bee6d032..e9d47e3d3b59 100644 --- a/drivers/memory/tegra/tegra30.c +++ b/drivers/memory/tegra/tegra30.c @@ -10,6 +10,46 @@ #include "mc.h" +#define MC_EMEM_ARB_CFG 0x90 +#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94 +#define MC_EMEM_ARB_TIMING_RCD 0x98 +#define MC_EMEM_ARB_TIMING_RP 0x9c +#define MC_EMEM_ARB_TIMING_RC 0xa0 +#define MC_EMEM_ARB_TIMING_RAS 0xa4 +#define MC_EMEM_ARB_TIMING_FAW 0xa8 +#define MC_EMEM_ARB_TIMING_RRD 0xac +#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0 +#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4 +#define MC_EMEM_ARB_TIMING_R2R 0xb8 +#define MC_EMEM_ARB_TIMING_W2W 0xbc +#define MC_EMEM_ARB_TIMING_R2W 0xc0 +#define MC_EMEM_ARB_TIMING_W2R 0xc4 +#define MC_EMEM_ARB_DA_TURNS 0xd0 +#define MC_EMEM_ARB_DA_COVERS 0xd4 +#define MC_EMEM_ARB_MISC0 0xd8 +#define MC_EMEM_ARB_RING1_THROTTLE 0xe0 + +static const unsigned long tegra30_mc_emem_regs[] = { + MC_EMEM_ARB_CFG, + MC_EMEM_ARB_OUTSTANDING_REQ, + MC_EMEM_ARB_TIMING_RCD, + MC_EMEM_ARB_TIMING_RP, + MC_EMEM_ARB_TIMING_RC, + MC_EMEM_ARB_TIMING_RAS, + MC_EMEM_ARB_TIMING_FAW, + MC_EMEM_ARB_TIMING_RRD, + MC_EMEM_ARB_TIMING_RAP2PRE, + MC_EMEM_ARB_TIMING_WAP2PRE, + MC_EMEM_ARB_TIMING_R2R, + MC_EMEM_ARB_TIMING_W2W, + MC_EMEM_ARB_TIMING_R2W, + MC_EMEM_ARB_TIMING_W2R, + MC_EMEM_ARB_DA_TURNS, + MC_EMEM_ARB_DA_COVERS, + MC_EMEM_ARB_MISC0, + MC_EMEM_ARB_RING1_THROTTLE, +}; + static const struct tegra_mc_client tegra30_mc_clients[] = { { .id = 0x00, @@ -997,6 +1037,8 @@ const struct tegra_mc_soc tegra30_mc_soc = { .atom_size = 16, .client_id_mask = 0x7f, .smmu = &tegra30_smmu_soc, + .emem_regs = tegra30_mc_emem_regs, + .num_emem_regs = ARRAY_SIZE(tegra30_mc_emem_regs), .intmask = MC_INT_INVALID_SMMU_PAGE | MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, .reset_ops = &tegra_mc_reset_ops_common, diff --git a/include/soc/tegra/mc.h b/include/soc/tegra/mc.h index 16e2c2fb5f6c..1238e35653d1 100644 --- a/include/soc/tegra/mc.h +++ b/include/soc/tegra/mc.h @@ -181,7 +181,7 @@ struct tegra_mc { spinlock_t lock; }; -void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate); +int tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate); unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc); #endif /* __SOC_TEGRA_MC_H__ */ -- cgit v1.2.3-59-g8ed1b