]> git.karo-electronics.de Git - linux-beck.git/commitdiff
drm/msm/hdmi: Create a separate HDMI PHY driver
authorArchit Taneja <architt@codeaurora.org>
Thu, 25 Feb 2016 05:52:38 +0000 (11:22 +0530)
committerRob Clark <robdclark@gmail.com>
Mon, 29 Feb 2016 14:48:30 +0000 (09:48 -0500)
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>
drivers/gpu/drm/msm/Makefile
drivers/gpu/drm/msm/hdmi/hdmi.c
drivers/gpu/drm/msm/hdmi/hdmi.h
drivers/gpu/drm/msm/hdmi/hdmi_phy.c [new file with mode: 0644]
drivers/gpu/drm/msm/hdmi/hdmi_phy_8960.c
drivers/gpu/drm/msm/hdmi/hdmi_phy_8x60.c
drivers/gpu/drm/msm/hdmi/hdmi_phy_8x74.c

index 065ad4138799927f35904e4f3d4cae8560a8688a..6ad0f7e625e7cdf73052390da1dc3e37dc83e35b 100644 (file)
@@ -12,6 +12,7 @@ msm-y := \
        hdmi/hdmi_connector.o \
        hdmi/hdmi_hdcp.o \
        hdmi/hdmi_i2c.o \
+       hdmi/hdmi_phy.o \
        hdmi/hdmi_phy_8960.o \
        hdmi/hdmi_phy_8x60.o \
        hdmi/hdmi_phy_8x74.o \
index 68cc3cdefe921515606f5a130b7b0fa44e1d1627..0fe5411c5c3e4ffba530f62548cb7c3ab15c2eaf 100644 (file)
@@ -502,10 +502,12 @@ static struct platform_driver hdmi_driver = {
 
 void __init hdmi_register(void)
 {
+       hdmi_phy_driver_register();
        platform_driver_register(&hdmi_driver);
 }
 
 void __exit hdmi_unregister(void)
 {
        platform_driver_unregister(&hdmi_driver);
+       hdmi_phy_driver_unregister();
 }
index d71533219877e4519d8dcdf8509432be99258d39..3404235bb24dc4381f7b650c0dbfc816f21064ac 100644 (file)
@@ -139,25 +139,65 @@ static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg)
 }
 
 /*
- * The phy appears to be different, for example between 8960 and 8x60,
- * so split the phy related functions out and load the correct one at
- * runtime:
+ * hdmi phy:
  */
-
 struct hdmi_phy_funcs {
        void (*destroy)(struct hdmi_phy *phy);
        void (*powerup)(struct hdmi_phy *phy, unsigned long int pixclock);
        void (*powerdown)(struct hdmi_phy *phy);
 };
 
+enum hdmi_phy_type {
+       MSM_HDMI_PHY_8x60,
+       MSM_HDMI_PHY_8960,
+       MSM_HDMI_PHY_8x74,
+       MSM_HDMI_PHY_MAX,
+};
+
+struct hdmi_phy_cfg {
+       enum hdmi_phy_type type;
+       void (*powerup)(struct hdmi_phy *phy, unsigned long int pixclock);
+       void (*powerdown)(struct hdmi_phy *phy);
+       const char * const *reg_names;
+       int num_regs;
+       const char * const *clk_names;
+       int num_clks;
+};
+
+extern const struct hdmi_phy_cfg hdmi_phy_8x60_cfg;
+extern const struct hdmi_phy_cfg hdmi_phy_8960_cfg;
+extern const struct hdmi_phy_cfg hdmi_phy_8x74_cfg;
+
 struct hdmi_phy {
+       struct platform_device *pdev;
+       void __iomem *mmio;
+       struct hdmi_phy_cfg *cfg;
        const struct hdmi_phy_funcs *funcs;
+       struct regulator **regs;
+       struct clk **clks;
 };
 
 struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi);
 struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi);
 struct hdmi_phy *hdmi_phy_8x74_init(struct hdmi *hdmi);
 
+static inline void hdmi_phy_write(struct hdmi_phy *phy, u32 reg, u32 data)
+{
+       msm_writel(data, phy->mmio + reg);
+}
+
+static inline u32 hdmi_phy_read(struct hdmi_phy *phy, u32 reg)
+{
+       return msm_readl(phy->mmio + reg);
+}
+
+int hdmi_phy_resource_enable(struct hdmi_phy *phy);
+void hdmi_phy_resource_disable(struct hdmi_phy *phy);
+void hdmi_phy_powerup(struct hdmi_phy *phy, unsigned long int pixclock);
+void hdmi_phy_powerdown(struct hdmi_phy *phy);
+void __init hdmi_phy_driver_register(void);
+void __exit hdmi_phy_driver_unregister(void);
+
 /*
  * audio:
  */
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_phy.c b/drivers/gpu/drm/msm/hdmi/hdmi_phy.c
new file mode 100644 (file)
index 0000000..de3f0f5
--- /dev/null
@@ -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);
+}
index 3a01cb5051e2db05b7757ee95d3d994aa393b625..cbdd70085d12ee06f3e2fb847ec89dbab52dd630 100644 (file)
@@ -464,6 +464,24 @@ static const struct hdmi_phy_funcs hdmi_phy_8960_funcs = {
                .powerdown = hdmi_phy_8960_powerdown,
 };
 
+static const char * const hdmi_phy_8960_reg_names[] = {
+       "core-vdda",
+};
+
+static const char * const hdmi_phy_8960_clk_names[] = {
+       "slave_iface_clk",
+};
+
+const struct hdmi_phy_cfg hdmi_phy_8960_cfg = {
+       .type = MSM_HDMI_PHY_8960,
+       .powerup = hdmi_phy_8960_powerup,
+       .powerdown = hdmi_phy_8960_powerdown,
+       .reg_names = hdmi_phy_8960_reg_names,
+       .num_regs = ARRAY_SIZE(hdmi_phy_8960_reg_names),
+       .clk_names = hdmi_phy_8960_clk_names,
+       .num_clks = ARRAY_SIZE(hdmi_phy_8960_clk_names),
+};
+
 struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi)
 {
        struct hdmi_phy_8960 *phy_8960;
index cb01421ae1e4ba9db3df0cdc0997e4d6591c2147..d529164f938db8fdd0e24caf488be7159cb75846 100644 (file)
@@ -155,6 +155,12 @@ static const struct hdmi_phy_funcs hdmi_phy_8x60_funcs = {
                .powerdown = hdmi_phy_8x60_powerdown,
 };
 
+const struct hdmi_phy_cfg hdmi_phy_8x60_cfg = {
+       .type = MSM_HDMI_PHY_8x60,
+       .powerup = hdmi_phy_8x60_powerup,
+       .powerdown = hdmi_phy_8x60_powerdown,
+};
+
 struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi)
 {
        struct hdmi_phy_8x60 *phy_8x60;
index 56ab8917ee9a353a7d497c0231fd9d5c07e2f768..5e42d92f3699b15e9c6065075a08f7b9ca7f7297 100644 (file)
@@ -67,6 +67,26 @@ static const struct hdmi_phy_funcs hdmi_phy_8x74_funcs = {
                .powerdown = hdmi_phy_8x74_powerdown,
 };
 
+static const char * const hdmi_phy_8x74_reg_names[] = {
+       "core-vdda",
+       "vddio",
+};
+
+static const char * const hdmi_phy_8x74_clk_names[] = {
+       "iface_clk",
+       "alt_iface_clk"
+};
+
+const struct hdmi_phy_cfg hdmi_phy_8x74_cfg = {
+       .type = MSM_HDMI_PHY_8x74,
+       .powerup = hdmi_phy_8x74_powerup,
+       .powerdown = hdmi_phy_8x74_powerdown,
+       .reg_names = hdmi_phy_8x74_reg_names,
+       .num_regs = ARRAY_SIZE(hdmi_phy_8x74_reg_names),
+       .clk_names = hdmi_phy_8x74_clk_names,
+       .num_clks = ARRAY_SIZE(hdmi_phy_8x74_clk_names),
+};
+
 struct hdmi_phy *hdmi_phy_8x74_init(struct hdmi *hdmi)
 {
        struct hdmi_phy_8x74 *phy_8x74;