linux/drivers/gpu/drm/msm/hdmi/hdmi.c
Rob Clark f6a8eaca0e drm/msm/mdp5: use irqdomains
For mdp5, the irqs of hdmi/eDP/dsi0/dsi1 blocks get routed through the
mdp block.  In order to decouple hdmi/eDP/etc, register an irq domain
in mdp5.  When hdmi/dsi/etc are used with mdp4, they can directly setup
their irqs in their DT nodes as normal.  When used with mdp5, instead
set the mdp device as the interrupt-parent, as in:

	mdp: qcom,mdss_mdp@fd900000 {
		compatible = "qcom,mdss_mdp";
		interrupt-controller;
		#interrupt-cells = <1>;
		...
	};

	hdmi: qcom,hdmi_tx@fd922100 {
		compatible = "qcom,hdmi-tx-8074";
		interrupt-parent = <&mdp>;
		interrupts = <8 0>;   /* MDP5_HW_INTR_STATUS.INTR_HDMI */
		...
	};

There is a slight awkwardness, in that we cannot disable child irqs
at the mdp level, they can only be cleared in the child block.  So
you must not use threaded irq handlers in the child.  I'm not sure
if there is a better way to deal with that.

Signed-off-by: Rob Clark <robdclark@gmail.com>
2014-11-21 08:56:18 -05:00

443 lines
12 KiB
C

/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/of_irq.h>
#include "hdmi.h"
void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
{
uint32_t ctrl = 0;
if (power_on) {
ctrl |= HDMI_CTRL_ENABLE;
if (!hdmi->hdmi_mode) {
ctrl |= HDMI_CTRL_HDMI;
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
ctrl &= ~HDMI_CTRL_HDMI;
} else {
ctrl |= HDMI_CTRL_HDMI;
}
} else {
ctrl = HDMI_CTRL_HDMI;
}
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
DBG("HDMI Core: %s, HDMI_CTRL=0x%08x",
power_on ? "Enable" : "Disable", ctrl);
}
static irqreturn_t hdmi_irq(int irq, void *dev_id)
{
struct hdmi *hdmi = dev_id;
/* Process HPD: */
hdmi_connector_irq(hdmi->connector);
/* Process DDC: */
hdmi_i2c_irq(hdmi->i2c);
/* TODO audio.. */
return IRQ_HANDLED;
}
void hdmi_destroy(struct kref *kref)
{
struct hdmi *hdmi = container_of(kref, struct hdmi, refcount);
struct hdmi_phy *phy = hdmi->phy;
if (phy)
phy->funcs->destroy(phy);
if (hdmi->i2c)
hdmi_i2c_destroy(hdmi->i2c);
platform_set_drvdata(hdmi->pdev, NULL);
}
/* construct hdmi at bind/probe time, grab all the resources. If
* we are to EPROBE_DEFER we want to do it here, rather than later
* at modeset_init() time
*/
static struct hdmi *hdmi_init(struct platform_device *pdev)
{
struct hdmi_platform_config *config = pdev->dev.platform_data;
struct hdmi *hdmi = NULL;
int i, ret;
hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi) {
ret = -ENOMEM;
goto fail;
}
kref_init(&hdmi->refcount);
hdmi->pdev = pdev;
hdmi->config = config;
/* not sure about which phy maps to which msm.. probably I miss some */
if (config->phy_init)
hdmi->phy = config->phy_init(hdmi);
else
hdmi->phy = ERR_PTR(-ENXIO);
if (IS_ERR(hdmi->phy)) {
ret = PTR_ERR(hdmi->phy);
dev_err(&pdev->dev, "failed to load phy: %d\n", ret);
hdmi->phy = NULL;
goto fail;
}
hdmi->mmio = msm_ioremap(pdev, config->mmio_name, "HDMI");
if (IS_ERR(hdmi->mmio)) {
ret = PTR_ERR(hdmi->mmio);
goto fail;
}
BUG_ON(config->hpd_reg_cnt > ARRAY_SIZE(hdmi->hpd_regs));
for (i = 0; i < config->hpd_reg_cnt; i++) {
struct regulator *reg;
reg = devm_regulator_get(&pdev->dev,
config->hpd_reg_names[i]);
if (IS_ERR(reg)) {
ret = PTR_ERR(reg);
dev_err(&pdev->dev, "failed to get hpd regulator: %s (%d)\n",
config->hpd_reg_names[i], ret);
goto fail;
}
hdmi->hpd_regs[i] = reg;
}
BUG_ON(config->pwr_reg_cnt > ARRAY_SIZE(hdmi->pwr_regs));
for (i = 0; i < config->pwr_reg_cnt; i++) {
struct regulator *reg;
reg = devm_regulator_get(&pdev->dev,
config->pwr_reg_names[i]);
if (IS_ERR(reg)) {
ret = PTR_ERR(reg);
dev_err(&pdev->dev, "failed to get pwr regulator: %s (%d)\n",
config->pwr_reg_names[i], ret);
goto fail;
}
hdmi->pwr_regs[i] = reg;
}
BUG_ON(config->hpd_clk_cnt > ARRAY_SIZE(hdmi->hpd_clks));
for (i = 0; i < config->hpd_clk_cnt; i++) {
struct clk *clk;
clk = devm_clk_get(&pdev->dev, config->hpd_clk_names[i]);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
dev_err(&pdev->dev, "failed to get hpd clk: %s (%d)\n",
config->hpd_clk_names[i], ret);
goto fail;
}
hdmi->hpd_clks[i] = clk;
}
BUG_ON(config->pwr_clk_cnt > ARRAY_SIZE(hdmi->pwr_clks));
for (i = 0; i < config->pwr_clk_cnt; i++) {
struct clk *clk;
clk = devm_clk_get(&pdev->dev, config->pwr_clk_names[i]);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
dev_err(&pdev->dev, "failed to get pwr clk: %s (%d)\n",
config->pwr_clk_names[i], ret);
goto fail;
}
hdmi->pwr_clks[i] = clk;
}
hdmi->i2c = hdmi_i2c_init(hdmi);
if (IS_ERR(hdmi->i2c)) {
ret = PTR_ERR(hdmi->i2c);
dev_err(&pdev->dev, "failed to get i2c: %d\n", ret);
hdmi->i2c = NULL;
goto fail;
}
return hdmi;
fail:
if (hdmi)
hdmi_destroy(&hdmi->refcount);
return ERR_PTR(ret);
}
/* Second part of initialization, the drm/kms level modeset_init,
* constructs/initializes mode objects, etc, is called from master
* driver (not hdmi sub-device's probe/bind!)
*
* Any resource (regulator/clk/etc) which could be missing at boot
* should be handled in hdmi_init() so that failure happens from
* hdmi sub-device's probe.
*/
int hdmi_modeset_init(struct hdmi *hdmi,
struct drm_device *dev, struct drm_encoder *encoder)
{
struct msm_drm_private *priv = dev->dev_private;
struct platform_device *pdev = hdmi->pdev;
int ret;
hdmi->dev = dev;
hdmi->encoder = encoder;
hdmi_audio_infoframe_init(&hdmi->audio.infoframe);
hdmi->bridge = hdmi_bridge_init(hdmi);
if (IS_ERR(hdmi->bridge)) {
ret = PTR_ERR(hdmi->bridge);
dev_err(dev->dev, "failed to create HDMI bridge: %d\n", ret);
hdmi->bridge = NULL;
goto fail;
}
hdmi->connector = hdmi_connector_init(hdmi);
if (IS_ERR(hdmi->connector)) {
ret = PTR_ERR(hdmi->connector);
dev_err(dev->dev, "failed to create HDMI connector: %d\n", ret);
hdmi->connector = NULL;
goto fail;
}
hdmi->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (hdmi->irq < 0) {
ret = hdmi->irq;
dev_err(dev->dev, "failed to get irq: %d\n", ret);
goto fail;
}
ret = devm_request_irq(&pdev->dev, hdmi->irq,
hdmi_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"hdmi_isr", hdmi);
if (ret < 0) {
dev_err(dev->dev, "failed to request IRQ%u: %d\n",
hdmi->irq, ret);
goto fail;
}
encoder->bridge = hdmi->bridge;
priv->bridges[priv->num_bridges++] = hdmi->bridge;
priv->connectors[priv->num_connectors++] = hdmi->connector;
platform_set_drvdata(pdev, hdmi);
return 0;
fail:
/* bridge/connector are normally destroyed by drm: */
if (hdmi->bridge) {
hdmi->bridge->funcs->destroy(hdmi->bridge);
hdmi->bridge = NULL;
}
if (hdmi->connector) {
hdmi->connector->funcs->destroy(hdmi->connector);
hdmi->connector = NULL;
}
return ret;
}
/*
* The hdmi device:
*/
#include <linux/of_gpio.h>
static void set_hdmi(struct drm_device *dev, struct hdmi *hdmi)
{
struct msm_drm_private *priv = dev->dev_private;
priv->hdmi = hdmi;
}
#ifdef CONFIG_OF
static int get_gpio(struct device *dev, struct device_node *of_node, const char *name)
{
int gpio = of_get_named_gpio(of_node, name, 0);
if (gpio < 0) {
char name2[32];
snprintf(name2, sizeof(name2), "%s-gpio", name);
gpio = of_get_named_gpio(of_node, name2, 0);
if (gpio < 0) {
dev_err(dev, "failed to get gpio: %s (%d)\n",
name, gpio);
gpio = -1;
}
}
return gpio;
}
#endif
static int hdmi_bind(struct device *dev, struct device *master, void *data)
{
static struct hdmi_platform_config config = {};
struct hdmi *hdmi;
#ifdef CONFIG_OF
struct device_node *of_node = dev->of_node;
if (of_device_is_compatible(of_node, "qcom,hdmi-tx-8074")) {
static const char *hpd_reg_names[] = {"hpd-gdsc", "hpd-5v"};
static const char *pwr_reg_names[] = {"core-vdda", "core-vcc"};
static const char *hpd_clk_names[] = {"iface_clk", "core_clk", "mdp_core_clk"};
static unsigned long hpd_clk_freq[] = {0, 19200000, 0};
static const char *pwr_clk_names[] = {"extp_clk", "alt_iface_clk"};
config.phy_init = hdmi_phy_8x74_init;
config.hpd_reg_names = hpd_reg_names;
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
config.pwr_reg_names = pwr_reg_names;
config.pwr_reg_cnt = ARRAY_SIZE(pwr_reg_names);
config.hpd_clk_names = hpd_clk_names;
config.hpd_freq = hpd_clk_freq;
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
config.pwr_clk_names = pwr_clk_names;
config.pwr_clk_cnt = ARRAY_SIZE(pwr_clk_names);
} else if (of_device_is_compatible(of_node, "qcom,hdmi-tx-8960")) {
static const char *hpd_clk_names[] = {"core_clk", "master_iface_clk", "slave_iface_clk"};
static const char *hpd_reg_names[] = {"core-vdda", "hdmi-mux"};
config.phy_init = hdmi_phy_8960_init;
config.hpd_reg_names = hpd_reg_names;
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
config.hpd_clk_names = hpd_clk_names;
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
} else if (of_device_is_compatible(of_node, "qcom,hdmi-tx-8660")) {
config.phy_init = hdmi_phy_8x60_init;
} else {
dev_err(dev, "unknown phy: %s\n", of_node->name);
}
config.mmio_name = "core_physical";
config.ddc_clk_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-clk");
config.ddc_data_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-data");
config.hpd_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-hpd");
config.mux_en_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-mux-en");
config.mux_sel_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-mux-sel");
config.mux_lpm_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-mux-lpm");
#else
static const char *hpd_clk_names[] = {
"core_clk", "master_iface_clk", "slave_iface_clk",
};
if (cpu_is_apq8064()) {
static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
config.phy_init = hdmi_phy_8960_init;
config.mmio_name = "hdmi_msm_hdmi_addr";
config.hpd_reg_names = hpd_reg_names;
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
config.hpd_clk_names = hpd_clk_names;
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
config.ddc_clk_gpio = 70;
config.ddc_data_gpio = 71;
config.hpd_gpio = 72;
config.mux_en_gpio = -1;
config.mux_sel_gpio = -1;
} else if (cpu_is_msm8960() || cpu_is_msm8960ab()) {
static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
config.phy_init = hdmi_phy_8960_init;
config.mmio_name = "hdmi_msm_hdmi_addr";
config.hpd_reg_names = hpd_reg_names;
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
config.hpd_clk_names = hpd_clk_names;
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
config.ddc_clk_gpio = 100;
config.ddc_data_gpio = 101;
config.hpd_gpio = 102;
config.mux_en_gpio = -1;
config.mux_sel_gpio = -1;
} else if (cpu_is_msm8x60()) {
static const char *hpd_reg_names[] = {
"8901_hdmi_mvs", "8901_mpp0"
};
config.phy_init = hdmi_phy_8x60_init;
config.mmio_name = "hdmi_msm_hdmi_addr";
config.hpd_reg_names = hpd_reg_names;
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
config.hpd_clk_names = hpd_clk_names;
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
config.ddc_clk_gpio = 170;
config.ddc_data_gpio = 171;
config.hpd_gpio = 172;
config.mux_en_gpio = -1;
config.mux_sel_gpio = -1;
}
#endif
dev->platform_data = &config;
hdmi = hdmi_init(to_platform_device(dev));
if (IS_ERR(hdmi))
return PTR_ERR(hdmi);
set_hdmi(dev_get_drvdata(master), hdmi);
return 0;
}
static void hdmi_unbind(struct device *dev, struct device *master,
void *data)
{
set_hdmi(dev_get_drvdata(master), NULL);
}
static const struct component_ops hdmi_ops = {
.bind = hdmi_bind,
.unbind = hdmi_unbind,
};
static int hdmi_dev_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &hdmi_ops);
}
static int hdmi_dev_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &hdmi_ops);
return 0;
}
static const struct of_device_id dt_match[] = {
{ .compatible = "qcom,hdmi-tx-8074" },
{ .compatible = "qcom,hdmi-tx-8960" },
{ .compatible = "qcom,hdmi-tx-8660" },
{}
};
static struct platform_driver hdmi_driver = {
.probe = hdmi_dev_probe,
.remove = hdmi_dev_remove,
.driver = {
.name = "hdmi_msm",
.of_match_table = dt_match,
},
};
void __init hdmi_register(void)
{
platform_driver_register(&hdmi_driver);
}
void __exit hdmi_unregister(void)
{
platform_driver_unregister(&hdmi_driver);
}