diff options
author | Archit Taneja <architt@codeaurora.org> | 2016-02-25 11:22:38 +0530 |
---|---|---|
committer | Rob Clark <robdclark@gmail.com> | 2016-02-29 09:48:30 -0500 |
commit | 15b4a452385955f3ae4477a079f02c5ff168d310 (patch) | |
tree | 187cb03895833f0fab81a9b33d171a8fde7786a7 /drivers/gpu/drm/msm/hdmi/hdmi_phy.c | |
parent | c95ea16254c7affa1654804c8542d1f054a40f9f (diff) |
drm/msm/hdmi: Create a separate HDMI PHY driver
Create a PHY device that represents the TX PHY and PLL parts of the HDMI
block.
This makes management of PHY specific resources (regulators and clocks)
much easier, and makes the PHY and PLL usable independently. It also
simplifies the core HDMI driver, which currently assigns phy ops among
many other things.
The PHY driver implementation done here is very similar to the PHY driver
we already have for DSI.
Keep the old hdmi_phy_funcs ops for now. The driver will use these until
the HDMI PHY/PLL register offsets aren't considered as separate
domains (i.e. their offsets start from 0).
The driver doesn't use the common PHY framework for now. This is because
it's hard to map our ops with the ops provided by the framework. The
bindings used for this is the generic phy bindings. So, this can be
adapted to the PHY framework in the future, if possible.
Signed-off-by: Archit Taneja <architt@codeaurora.org>
Signed-off-by: Rob Clark <robdclark@gmail.com>
Diffstat (limited to 'drivers/gpu/drm/msm/hdmi/hdmi_phy.c')
-rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi_phy.c | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c new file mode 100644 index 000000000000..de3f0f504ede --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2016, 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. + */ + +#include <linux/of_device.h> + +#include "hdmi.h" + +static int hdmi_phy_resource_init(struct hdmi_phy *phy) +{ + struct hdmi_phy_cfg *cfg = phy->cfg; + struct device *dev = &phy->pdev->dev; + int i, ret; + + phy->regs = devm_kzalloc(dev, sizeof(phy->regs[0]) * cfg->num_regs, + GFP_KERNEL); + if (!phy->regs) + return -ENOMEM; + + phy->clks = devm_kzalloc(dev, sizeof(phy->clks[0]) * cfg->num_clks, + GFP_KERNEL); + if (!phy->clks) + return -ENOMEM; + + for (i = 0; i < cfg->num_regs; i++) { + struct regulator *reg; + + reg = devm_regulator_get(dev, cfg->reg_names[i]); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + dev_err(dev, "failed to get phy regulator: %s (%d)\n", + cfg->reg_names[i], ret); + return ret; + } + + phy->regs[i] = reg; + } + + for (i = 0; i < cfg->num_clks; i++) { + struct clk *clk; + + clk = devm_clk_get(dev, cfg->clk_names[i]); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(dev, "failed to get phy clock: %s (%d)\n", + cfg->clk_names[i], ret); + return ret; + } + + phy->clks[i] = clk; + } + + return 0; +} + +int hdmi_phy_resource_enable(struct hdmi_phy *phy) +{ + struct hdmi_phy_cfg *cfg = phy->cfg; + struct device *dev = &phy->pdev->dev; + int i, ret = 0; + + pm_runtime_get_sync(dev); + + for (i = 0; i < cfg->num_regs; i++) { + ret = regulator_enable(phy->regs[i]); + if (ret) + dev_err(dev, "failed to enable regulator: %s (%d)\n", + cfg->reg_names[i], ret); + } + + for (i = 0; i < cfg->num_clks; i++) { + ret = clk_prepare_enable(phy->clks[i]); + if (ret) + dev_err(dev, "failed to enable clock: %s (%d)\n", + cfg->clk_names[i], ret); + } + + return ret; +} + +void hdmi_phy_resource_disable(struct hdmi_phy *phy) +{ + struct hdmi_phy_cfg *cfg = phy->cfg; + struct device *dev = &phy->pdev->dev; + int i; + + for (i = cfg->num_clks - 1; i >= 0; i--) + clk_disable_unprepare(phy->clks[i]); + + for (i = cfg->num_regs - 1; i >= 0; i--) + regulator_disable(phy->regs[i]); + + pm_runtime_put_sync(dev); +} + +void hdmi_phy_powerup(struct hdmi_phy *phy, unsigned long int pixclock) +{ + if (!phy || !phy->cfg->powerup) + return; + + phy->cfg->powerup(phy, pixclock); +} + +void hdmi_phy_powerdown(struct hdmi_phy *phy) +{ + if (!phy || !phy->cfg->powerdown) + return; + + phy->cfg->powerdown(phy); +} + +static int hdmi_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hdmi_phy *phy; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENODEV; + + phy->cfg = (struct hdmi_phy_cfg *)of_device_get_match_data(dev); + if (!phy->cfg) + return -ENODEV; + + phy->mmio = msm_ioremap(pdev, "hdmi_phy", "HDMI_PHY"); + if (IS_ERR(phy->mmio)) { + dev_err(dev, "%s: failed to map phy base\n", __func__); + return -ENOMEM; + } + + phy->pdev = pdev; + + ret = hdmi_phy_resource_init(phy); + if (ret) + return ret; + + pm_runtime_enable(&pdev->dev); + + platform_set_drvdata(pdev, phy); + + return 0; +} + +static int hdmi_phy_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id hdmi_phy_dt_match[] = { + { .compatible = "qcom,hdmi-phy-8660", + .data = &hdmi_phy_8x60_cfg }, + { .compatible = "qcom,hdmi-phy-8960", + .data = &hdmi_phy_8960_cfg }, + { .compatible = "qcom,hdmi-phy-8974", + .data = &hdmi_phy_8x74_cfg }, + { .compatible = "qcom,hdmi-phy-8084", + .data = &hdmi_phy_8x74_cfg }, + {} +}; + +static struct platform_driver hdmi_phy_platform_driver = { + .probe = hdmi_phy_probe, + .remove = hdmi_phy_remove, + .driver = { + .name = "msm_hdmi_phy", + .of_match_table = hdmi_phy_dt_match, + }, +}; + +void __init hdmi_phy_driver_register(void) +{ + platform_driver_register(&hdmi_phy_platform_driver); +} + +void __exit hdmi_phy_driver_unregister(void) +{ + platform_driver_unregister(&hdmi_phy_platform_driver); +} |