/* * 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 #include #include #include #include #include #include #include #include #include #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_root_clk"); 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 = regmap_init_mmio_clk(NULL, NULL, 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 "); MODULE_LICENSE("GPL");