summaryrefslogtreecommitdiff
path: root/drivers/reset
diff options
context:
space:
mode:
authorFancy Fang <chen.fang@nxp.com>2019-06-05 18:04:17 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2019-11-25 15:45:06 +0800
commit6d73c7f3ae06752a6f2afd0815f436a3e8079c05 (patch)
tree8fb93711635aca3c0dd5b4a34b1156b757a3f190 /drivers/reset
parenta9c85cb2d8ed9b8dce34bcbe2e7cc13790350a82 (diff)
reset: Add driver for dispmix reset
This is an reset driver to implement a reset controller device DISPMIX on IMX8MM and IMX8MN platforms. Dispmix reset is used to reset or enable related buses and clks for the submodules in DISPMIX. All the dispmix resets are divided into three subgroups: sft_rstn, clk_en and mipi_rst, and each of them contains several reset lines to control several different modules on and off in DISPMIX which doesn't require the standard reset flow, but only line assert and deassert operations. Signed-off-by: Fancy Fang <chen.fang@nxp.com>
Diffstat (limited to 'drivers/reset')
-rw-r--r--drivers/reset/Kconfig9
-rw-r--r--drivers/reset/Makefile1
-rw-r--r--drivers/reset/reset-dispmix.c399
3 files changed, 409 insertions, 0 deletions
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 1966e4504aa5..a3ecc036b046 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -49,6 +49,15 @@ config RESET_BRCMSTB
This enables the reset controller driver for Broadcom STB SoCs using
a SUN_TOP_CTRL_SW_INIT style controller.
+config RESET_DISPMIX
+ tristate "IMX Display Mix reset support"
+ default y
+ select REGMAP_MMIO
+ depends on ARCH_FSL_IMX8MM || ARCH_FSL_IMX8MN
+ help
+ This driver provides support for Display Mix reset that is controlled
+ by dispmix GPR registers.
+
config RESET_HSDK
bool "Synopsys HSDK Reset Driver"
depends on HAS_IOMEM
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 81721c09618d..f3ae62d95be9 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_RESET_ATH79) += reset-ath79.o
obj-$(CONFIG_RESET_AXS10X) += reset-axs10x.o
obj-$(CONFIG_RESET_BERLIN) += reset-berlin.o
obj-$(CONFIG_RESET_BRCMSTB) += reset-brcmstb.o
+obj-$(CONFIG_RESET_DISPMIX) += reset-dispmix.o
obj-$(CONFIG_RESET_HSDK) += reset-hsdk.o
obj-$(CONFIG_RESET_IMX7) += reset-imx7.o
obj-$(CONFIG_RESET_LANTIQ) += reset-lantiq.o
diff --git a/drivers/reset/reset-dispmix.c b/drivers/reset/reset-dispmix.c
new file mode 100644
index 000000000000..fedb3a3e6b42
--- /dev/null
+++ b/drivers/reset/reset-dispmix.c
@@ -0,0 +1,399 @@
+/*
+ * IMX Display Mix GPR reset driver
+ *
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset-controller.h>
+#include <dt-bindings/reset/imx8mm-dispmix.h>
+#include <dt-bindings/reset/imx8mn-dispmix.h>
+
+#define DRIVER_NAME "dispmix_reset_drv"
+
+/* DISPMIX GPR registers */
+#define DISPLAY_MIX_SFT_RSTN_CSR 0x00
+#define DISPLAY_MIX_CLK_EN_CSR 0x00
+#define GPR_MIPI_RESET_DIV 0x00
+
+struct dispmix_reset_controller {
+ struct reset_controller_dev rcdev;
+ struct device *dev;
+ struct regmap *rstcon;
+ struct clk *ipg_clk;
+ bool active_low;
+};
+
+struct dispmix_reset_entry {
+ uint32_t reg_off;
+ uint32_t bit_off;
+};
+
+struct dispmix_reset_pdata {
+ const struct dispmix_reset_entry *resets;
+ uint32_t nr_resets;
+ const struct regmap_config *config;
+};
+
+#define RESET_ENTRY(id, reg, bit) \
+ [id] = { .reg_off = (reg), .bit_off = (bit) }
+
+static const struct dispmix_reset_entry imx8mm_sft_rstn[] = {
+ /* dispmix reset entry */
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_CHIP_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 0),
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_IPG_HARD_ASYNC_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 1),
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_CSI_HRESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 2),
+ RESET_ENTRY(IMX8MM_CAMERA_PIXEL_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 3),
+ RESET_ENTRY(IMX8MM_MIPI_CSI_I_PRESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 4),
+ RESET_ENTRY(IMX8MM_MIPI_DSI_I_PRESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 5),
+ RESET_ENTRY(IMX8MM_BUS_RSTN_BLK_SYNC,
+ DISPLAY_MIX_SFT_RSTN_CSR, 6),
+};
+
+static const struct dispmix_reset_entry imx8mm_clk_en[] = {
+ /* dispmix clock enable entry */
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_CSI_HCLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 0),
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_SPU_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 1),
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_MEM_WRAPPER_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 2),
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_IPG_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 3),
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_IPG_CLK_S_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 4),
+ RESET_ENTRY(IMX8MM_CSI_BRIDGE_IPG_CLK_S_RAW_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 5),
+ RESET_ENTRY(IMX8MM_LCDIF_APB_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 6),
+ RESET_ENTRY(IMX8MM_LCDIF_PIXEL_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 7),
+ RESET_ENTRY(IMX8MM_MIPI_DSI_PCLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 8),
+ RESET_ENTRY(IMX8MM_MIPI_DSI_CLKREF_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 9),
+ RESET_ENTRY(IMX8MM_MIPI_CSI_ACLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 10),
+ RESET_ENTRY(IMX8MM_MIPI_CSI_PCLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 11),
+ RESET_ENTRY(IMX8MM_BUS_BLK_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 12),
+};
+
+static const struct dispmix_reset_entry imx8mm_mipi_rst[] = {
+ /* mipi lanes reset entry */
+ RESET_ENTRY(IMX8MM_MIPI_S_RESET,
+ GPR_MIPI_RESET_DIV, 16),
+ RESET_ENTRY(IMX8MM_MIPI_M_RESET,
+ GPR_MIPI_RESET_DIV, 17),
+};
+
+static const struct dispmix_reset_entry imx8mn_sft_rstn[] = {
+ /* dispmix reset entry */
+ RESET_ENTRY(IMX8MN_MIPI_DSI_PCLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 0),
+ RESET_ENTRY(IMX8MN_MIPI_DSI_CLKREF_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 1),
+ RESET_ENTRY(IMX8MN_MIPI_CSI_PCLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 2),
+ RESET_ENTRY(IMX8MN_MIPI_CSI_ACLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 3),
+ RESET_ENTRY(IMX8MN_LCDIF_PIXEL_CLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 4),
+ RESET_ENTRY(IMX8MN_LCDIF_APB_CLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 5),
+ RESET_ENTRY(IMX8MN_ISI_PROC_CLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 6),
+ RESET_ENTRY(IMX8MN_ISI_APB_CLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 7),
+ RESET_ENTRY(IMX8MN_BUS_BLK_CLK_RESET,
+ DISPLAY_MIX_SFT_RSTN_CSR, 8),
+};
+
+static const struct dispmix_reset_entry imx8mn_clk_en[] = {
+ /* dispmix clock enable entry */
+ RESET_ENTRY(IMX8MN_MIPI_DSI_PCLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 0),
+ RESET_ENTRY(IMX8MN_MIPI_DSI_CLKREF_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 1),
+ RESET_ENTRY(IMX8MN_MIPI_CSI_PCLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 2),
+ RESET_ENTRY(IMX8MN_MIPI_CSI_ACLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 3),
+ RESET_ENTRY(IMX8MN_LCDIF_PIXEL_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 4),
+ RESET_ENTRY(IMX8MN_LCDIF_APB_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 5),
+ RESET_ENTRY(IMX8MN_ISI_PROC_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 6),
+ RESET_ENTRY(IMX8MN_ISI_APB_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 7),
+ RESET_ENTRY(IMX8MN_BUS_BLK_CLK_EN,
+ DISPLAY_MIX_CLK_EN_CSR, 8),
+};
+
+static const struct dispmix_reset_entry imx8mn_mipi_rst[] = {
+ /* mipi lanes reset entry */
+ RESET_ENTRY(IMX8MN_MIPI_S_RESET,
+ GPR_MIPI_RESET_DIV, 16),
+ RESET_ENTRY(IMX8MN_MIPI_M_RESET,
+ GPR_MIPI_RESET_DIV, 17),
+};
+
+static const struct regmap_config sft_rstn_config = {
+ .name = "sft_rstn",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x00,
+};
+
+static const struct regmap_config clk_en_config = {
+ .name = "clk_en",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x00,
+};
+
+static const struct regmap_config mipi_rst_config = {
+ .name = "mipi_rst",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x00,
+};
+
+static const struct dispmix_reset_pdata imx8mm_sft_rstn_pdata = {
+ .resets = imx8mm_sft_rstn,
+ .nr_resets = IMX8MM_DISPMIX_SFT_RSTN_NUM,
+ .config = &sft_rstn_config,
+};
+
+static const struct dispmix_reset_pdata imx8mm_clk_en_pdata = {
+ .resets = imx8mm_clk_en,
+ .nr_resets = IMX8MM_DISPMIX_CLK_EN_NUM,
+ .config = &clk_en_config,
+};
+
+static const struct dispmix_reset_pdata imx8mm_mipi_rst_pdata = {
+ .resets = imx8mm_mipi_rst,
+ .nr_resets = IMX8MM_MIPI_RESET_NUM,
+ .config = &mipi_rst_config,
+};
+
+static const struct dispmix_reset_pdata imx8mn_sft_rstn_pdata = {
+ .resets = imx8mn_sft_rstn,
+ .nr_resets = IMX8MN_DISPMIX_SFT_RSTN_NUM,
+ .config = &sft_rstn_config,
+};
+
+static const struct dispmix_reset_pdata imx8mn_clk_en_pdata = {
+ .resets = imx8mn_clk_en,
+ .nr_resets = IMX8MN_DISPMIX_CLK_EN_NUM,
+ .config = &clk_en_config,
+};
+
+static const struct dispmix_reset_pdata imx8mn_mipi_rst_pdata = {
+ .resets = imx8mn_mipi_rst,
+ .nr_resets = IMX8MN_MIPI_RESET_NUM,
+ .config = &mipi_rst_config,
+};
+
+static const struct of_device_id dispmix_reset_dt_ids[] = {
+ {
+ .compatible = "fsl,imx8mm-dispmix-sft-rstn",
+ .data = &imx8mm_sft_rstn_pdata,
+ },
+ {
+ .compatible = "fsl,imx8mm-dispmix-clk-en",
+ .data = &imx8mm_clk_en_pdata,
+ },
+ {
+ .compatible = "fsl,imx8mm-dispmix-mipi-rst",
+ .data = &imx8mm_mipi_rst_pdata,
+ },
+ {
+ .compatible = "fsl,imx8mn-dispmix-sft-rstn",
+ .data = &imx8mn_sft_rstn_pdata,
+ },
+ {
+ .compatible = "fsl,imx8mn-dispmix-clk-en",
+ .data = &imx8mn_clk_en_pdata,
+ },
+ {
+ .compatible = "fsl,imx8mn-dispmix-mipi-rst",
+ .data = &imx8mn_mipi_rst_pdata,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dispmix_reset_dt_ids);
+
+static int dispmix_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct dispmix_reset_controller *drcdev;
+ const struct of_device_id *of_id;
+ const struct dispmix_reset_pdata *pdata;
+ const struct dispmix_reset_entry *rstent;
+ struct regmap *rstcon;
+
+ if (id >= rcdev->nr_resets) {
+ pr_info("dispmix reset: %lu is not a valid line\n", id);
+ return -EINVAL;
+ }
+
+ drcdev = container_of(rcdev, struct dispmix_reset_controller, rcdev);
+ of_id = of_match_device(dispmix_reset_dt_ids, drcdev->dev);
+ pdata = of_id->data;
+
+ rstcon = drcdev->rstcon;
+ rstent = &pdata->resets[id];
+
+ pm_runtime_get_sync(drcdev->dev);
+ regmap_update_bits(rstcon, rstent->reg_off,
+ 1 << rstent->bit_off,
+ !drcdev->active_low << rstent->bit_off);
+ pm_runtime_put(drcdev->dev);
+
+ return 0;
+}
+
+static int dispmix_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct dispmix_reset_controller *drcdev;
+ const struct of_device_id *of_id;
+ const struct dispmix_reset_pdata *pdata;
+ const struct dispmix_reset_entry *rstent;
+ struct regmap *rstcon;
+
+ if (id >= rcdev->nr_resets) {
+ pr_info("dispmix reset: %lu is not a valid line\n", id);
+ return -EINVAL;
+ }
+
+ drcdev = container_of(rcdev, struct dispmix_reset_controller, rcdev);
+ of_id = of_match_device(dispmix_reset_dt_ids, drcdev->dev);
+ pdata = of_id->data;
+
+ rstcon = drcdev->rstcon;
+ rstent = &pdata->resets[id];
+
+ pm_runtime_get_sync(drcdev->dev);
+ regmap_update_bits(rstcon, rstent->reg_off,
+ 1 << rstent->bit_off,
+ !!drcdev->active_low << rstent->bit_off);
+ pm_runtime_put(drcdev->dev);
+
+ return 0;
+}
+
+static const struct reset_control_ops dispmix_reset_ops = {
+ .assert = dispmix_reset_assert,
+ .deassert = dispmix_reset_deassert,
+};
+
+static int dispmix_reset_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct of_device_id *of_id;
+ struct dispmix_reset_controller *drcdev;
+ const struct dispmix_reset_pdata *pdata;
+ struct resource *res;
+ void __iomem *regs;
+ struct regmap *regmap;
+ struct clk *apb_clk;
+
+ drcdev = devm_kzalloc(dev, sizeof(*drcdev), GFP_KERNEL);
+ if (!drcdev)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ apb_clk = devm_clk_get(dev, "disp-apb");
+ if (IS_ERR(apb_clk)) {
+ dev_err(dev, "Unable to get disp apb clock\n");
+ return PTR_ERR(apb_clk);
+ }
+
+ drcdev->active_low = of_property_read_bool(np, "active_low");
+
+ of_id = of_match_device(dispmix_reset_dt_ids, dev);
+ pdata = of_id->data;
+
+ /* init mmio regmap */
+ regmap = devm_regmap_init_mmio_clk(dev, __clk_get_name(apb_clk),
+ regs, pdata->config);
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "Failed to init mmio regmap: %ld\n",
+ PTR_ERR(regmap));
+ return PTR_ERR(regmap);
+ }
+ drcdev->rstcon = regmap;
+
+ platform_set_drvdata(pdev, drcdev);
+ pm_runtime_enable(dev);
+
+ /* register reset controller */
+ drcdev->dev = dev;
+ drcdev->rcdev.of_node = dev->of_node;
+ drcdev->rcdev.owner = THIS_MODULE;
+ drcdev->rcdev.nr_resets = pdata->nr_resets;
+ drcdev->rcdev.ops = &dispmix_reset_ops;
+
+ return devm_reset_controller_register(dev, &drcdev->rcdev);
+}
+
+static int dispmix_reset_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver dispmix_reset_driver = {
+ .probe = dispmix_reset_probe,
+ .remove = dispmix_reset_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(dispmix_reset_dt_ids),
+ },
+};
+
+builtin_platform_driver(dispmix_reset_driver);
+
+MODULE_DESCRIPTION("IMX Display Mix reset driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");