/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include "mdss-pll.h" #include "mdss-dsi-pll.h" #include "mdss-hdmi-pll.h" int mdss_pll_resource_enable(struct mdss_pll_resources *pll_res, bool enable) { int rc = 0; int changed = 0; if (!pll_res) { pr_err("Invalid input parameters\n"); return -EINVAL; } /* * Don't turn off resources during handoff or add more than * 1 refcount. */ if (pll_res->handoff_resources && (!enable || (enable & pll_res->resource_enable))) { pr_debug("Do not turn on/off pll resources during handoff case\n"); return rc; } if (enable) { if (pll_res->resource_ref_cnt == 0) changed++; pll_res->resource_ref_cnt++; } else { if (pll_res->resource_ref_cnt) { pll_res->resource_ref_cnt--; if (pll_res->resource_ref_cnt == 0) changed++; } else { pr_err("PLL Resources already OFF\n"); } } if (changed) { rc = mdss_pll_util_resource_enable(pll_res, enable); if (rc) pr_err("Resource update failed rc=%d\n", rc); else pll_res->resource_enable = enable; } return rc; } static int mdss_pll_resource_init(struct platform_device *pdev, struct mdss_pll_resources *pll_res) { if (!pdev || !pll_res) { pr_err("Invalid input parameters\n"); return -EINVAL; } return mdss_pll_util_resource_init(pdev, pll_res); } static void mdss_pll_resource_deinit(struct platform_device *pdev, struct mdss_pll_resources *pll_res) { if (!pdev || !pll_res) { pr_err("Invalid input parameters\n"); return; } mdss_pll_util_resource_deinit(pdev, pll_res); } static void mdss_pll_resource_release(struct platform_device *pdev, struct mdss_pll_resources *pll_res) { if (!pdev || !pll_res) { pr_err("Invalid input parameters\n"); return; } mdss_pll_util_resource_release(pdev, pll_res); } static int mdss_pll_resource_parse(struct platform_device *pdev, struct mdss_pll_resources *pll_res) { int rc = 0; const char *compatible_stream; if (!pdev || !pll_res) { pr_err("Invalid input parameters\n"); return -EINVAL; } rc = mdss_pll_util_resource_parse(pdev, pll_res); if (rc) { pr_err("Failed to parse the resources rc=%d\n", rc); goto end; } compatible_stream = of_get_property(pdev->dev.of_node, "compatible", NULL); if (!compatible_stream) { pr_err("Failed to parse the compatible stream\n"); goto err; } if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_8952")) { pll_res->pll_interface_type = MDSS_DSI_PLL_LPM; pll_res->target_id = MDSS_PLL_TARGET_8952; } else if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_8937")) { pll_res->pll_interface_type = MDSS_DSI_PLL_LPM; pll_res->target_id = MDSS_PLL_TARGET_8937; } else if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_8996")) { pll_res->pll_interface_type = MDSS_DSI_PLL_8996; pll_res->target_id = MDSS_PLL_TARGET_8996; pll_res->revision = 1; } else if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_8996_v2")) { pll_res->pll_interface_type = MDSS_DSI_PLL_8996; pll_res->target_id = MDSS_PLL_TARGET_8996; pll_res->revision = 2; } else if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_titanium")) { pll_res->pll_interface_type = MDSS_DSI_PLL_8996; pll_res->target_id = MDSS_PLL_TARGET_TITANIUM; pll_res->revision = 2; } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8996")) { pll_res->pll_interface_type = MDSS_HDMI_PLL_8996; } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8996_v2")) { pll_res->pll_interface_type = MDSS_HDMI_PLL_8996_V2; } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8996_v3")) { pll_res->pll_interface_type = MDSS_HDMI_PLL_8996_V3; } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8996_v3_1p8")) { pll_res->pll_interface_type = MDSS_HDMI_PLL_8996_V3_1_8; } else { goto err; } return rc; err: mdss_pll_resource_release(pdev, pll_res); end: return rc; } static int mdss_pll_clock_register(struct platform_device *pdev, struct mdss_pll_resources *pll_res) { int rc; if (!pdev || !pll_res) { pr_err("Invalid input parameters\n"); return -EINVAL; } switch (pll_res->pll_interface_type) { case MDSS_DSI_PLL_LPM: rc = dsi_pll_clock_register_lpm(pdev, pll_res); break; case MDSS_DSI_PLL_8996: rc = dsi_pll_clock_register_8996(pdev, pll_res); break; case MDSS_HDMI_PLL_8996: rc = hdmi_8996_v1_pll_clock_register(pdev, pll_res); break; case MDSS_HDMI_PLL_8996_V2: rc = hdmi_8996_v2_pll_clock_register(pdev, pll_res); break; case MDSS_HDMI_PLL_8996_V3: rc = hdmi_8996_v3_pll_clock_register(pdev, pll_res); break; case MDSS_HDMI_PLL_8996_V3_1_8: rc = hdmi_8996_v3_1p8_pll_clock_register(pdev, pll_res); break; case MDSS_UNKNOWN_PLL: default: rc = -EINVAL; break; } if (rc) { pr_err("Pll ndx=%d clock register failed rc=%d\n", pll_res->index, rc); } return rc; } static int mdss_pll_probe(struct platform_device *pdev) { int rc = 0; const char *label; struct resource *pll_base_reg; struct resource *phy_base_reg; struct resource *dynamic_pll_base_reg; struct resource *gdsc_base_reg; struct mdss_pll_resources *pll_res; if (!pdev->dev.of_node) { pr_err("MDSS pll driver only supports device tree probe\n"); rc = -ENOTSUPP; goto error; } label = of_get_property(pdev->dev.of_node, "label", NULL); if (!label) pr_info("%d: MDSS pll label not specified\n", __LINE__); else pr_info("MDSS pll label = %s\n", label); pll_res = devm_kzalloc(&pdev->dev, sizeof(struct mdss_pll_resources), GFP_KERNEL); if (!pll_res) { pr_err("Failed to allocate the clock pll\n"); rc = -ENOMEM; goto error; } platform_set_drvdata(pdev, pll_res); rc = of_property_read_u32(pdev->dev.of_node, "cell-index", &pll_res->index); if (rc) { pr_err("Unable to get the cell-index rc=%d\n", rc); pll_res->index = 0; } pll_res->ssc_en = of_property_read_bool(pdev->dev.of_node, "qcom,dsi-pll-ssc-en"); if (pll_res->ssc_en) { pr_info("%s: label=%s PLL SSC enabled\n", __func__, label); rc = of_property_read_u32(pdev->dev.of_node, "qcom,ssc-frequency-hz", &pll_res->ssc_freq); rc = of_property_read_u32(pdev->dev.of_node, "qcom,ssc-ppm", &pll_res->ssc_ppm); pll_res->ssc_center = false; label = of_get_property(pdev->dev.of_node, "qcom,dsi-pll-ssc-mode", NULL); if (label && !strcmp(label, "center-spread")) pll_res->ssc_center = true; } pll_base_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pll_base"); if (!pll_base_reg) { pr_err("Unable to get the pll base resources\n"); rc = -ENOMEM; goto io_error; } pll_res->pll_base = ioremap(pll_base_reg->start, resource_size(pll_base_reg)); if (!pll_res->pll_base) { pr_err("Unable to remap pll base resources\n"); rc = -ENOMEM; goto io_error; } pr_debug("%s: ndx=%d base=%p\n", __func__, pll_res->index, pll_res->pll_base); rc = mdss_pll_resource_parse(pdev, pll_res); if (rc) { pr_err("Pll resource parsing from dt failed rc=%d\n", rc); goto res_parse_error; } phy_base_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_base"); if (phy_base_reg) { pll_res->phy_base = ioremap(phy_base_reg->start, resource_size(phy_base_reg)); if (!pll_res->phy_base) { pr_err("Unable to remap pll phy base resources\n"); rc = -ENOMEM; goto phy_io_error; } } dynamic_pll_base_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dynamic_pll_base"); if (dynamic_pll_base_reg) { pll_res->dyn_pll_base = ioremap(dynamic_pll_base_reg->start, resource_size(dynamic_pll_base_reg)); if (!pll_res->dyn_pll_base) { pr_err("Unable to remap dynamic pll base resources\n"); rc = -ENOMEM; goto dyn_pll_io_error; } } gdsc_base_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gdsc_base"); if (!gdsc_base_reg) { pr_err("Unable to get the gdsc base resource\n"); rc = -ENOMEM; goto gdsc_io_error; } pll_res->gdsc_base = ioremap(gdsc_base_reg->start, resource_size(gdsc_base_reg)); if (!pll_res->gdsc_base) { pr_err("Unable to remap gdsc base resources\n"); rc = -ENOMEM; goto gdsc_io_error; } rc = mdss_pll_resource_init(pdev, pll_res); if (rc) { pr_err("Pll ndx=%d resource init failed rc=%d\n", pll_res->index, rc); goto res_init_error; } rc = mdss_pll_clock_register(pdev, pll_res); if (rc) { pr_err("Pll ndx=%d clock register failed rc=%d\n", pll_res->index, rc); goto clock_register_error; } return rc; clock_register_error: mdss_pll_resource_deinit(pdev, pll_res); res_init_error: if (pll_res->gdsc_base) iounmap(pll_res->gdsc_base); gdsc_io_error: if (pll_res->dyn_pll_base) iounmap(pll_res->dyn_pll_base); dyn_pll_io_error: if (pll_res->phy_base) iounmap(pll_res->phy_base); phy_io_error: mdss_pll_resource_release(pdev, pll_res); res_parse_error: iounmap(pll_res->pll_base); io_error: devm_kfree(&pdev->dev, pll_res); error: return rc; } static int mdss_pll_remove(struct platform_device *pdev) { struct mdss_pll_resources *pll_res; pll_res = platform_get_drvdata(pdev); if (!pll_res) { pr_err("Invalid PLL resource data"); return 0; } mdss_pll_resource_deinit(pdev, pll_res); if (pll_res->phy_base) iounmap(pll_res->phy_base); if (pll_res->gdsc_base) iounmap(pll_res->gdsc_base); mdss_pll_resource_release(pdev, pll_res); iounmap(pll_res->pll_base); devm_kfree(&pdev->dev, pll_res); return 0; } static const struct of_device_id mdss_pll_dt_match[] = { {.compatible = "qcom,mdss_dsi_pll_8996"}, {.compatible = "qcom,mdss_dsi_pll_8996_v2"}, {.compatible = "qcom,mdss_hdmi_pll_8996"}, {.compatible = "qcom,mdss_hdmi_pll_8996_v2"}, {.compatible = "qcom,mdss_hdmi_pll_8996_v3"}, {.compatible = "qcom,mdss_hdmi_pll_8996_v3_1p8"}, {.compatible = "qcom,mdss_dsi_pll_8952"}, {.compatible = "qcom,mdss_dsi_pll_8937"}, {.compatible = "qcom,mdss_dsi_pll_titanium"}, {} }; MODULE_DEVICE_TABLE(of, mdss_clock_dt_match); static struct platform_driver mdss_pll_driver = { .probe = mdss_pll_probe, .remove = mdss_pll_remove, .driver = { .name = "mdss_pll", .of_match_table = mdss_pll_dt_match, }, }; static int __init mdss_pll_driver_init(void) { int rc; rc = platform_driver_register(&mdss_pll_driver); if (rc) pr_err("mdss_register_pll_driver() failed!\n"); return rc; } subsys_initcall(mdss_pll_driver_init); static void __exit mdss_pll_driver_deinit(void) { platform_driver_unregister(&mdss_pll_driver); } module_exit(mdss_pll_driver_deinit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("mdss pll driver");