2011-03-12 13:34:27 +07:00
|
|
|
/*
|
2013-09-12 19:15:57 +07:00
|
|
|
* HDMI interface DSS driver for TI's OMAP4 family of SoCs.
|
2017-12-06 03:29:31 +07:00
|
|
|
*
|
2011-03-12 13:34:27 +07:00
|
|
|
* Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/
|
|
|
|
* Authors: Yong Zhi
|
|
|
|
* Mythri pk <mythripk@ti.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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define DSS_SUBSYS_NAME "HDMI"
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/string.h>
|
2011-05-23 15:51:18 +07:00
|
|
|
#include <linux/platform_device.h>
|
2011-05-27 14:52:19 +07:00
|
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <linux/clk.h>
|
2012-04-26 18:48:32 +07:00
|
|
|
#include <linux/gpio.h>
|
2012-08-15 19:55:04 +07:00
|
|
|
#include <linux/regulator/consumer.h>
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
#include <linux/component.h>
|
2016-03-18 14:02:18 +07:00
|
|
|
#include <linux/of.h>
|
2017-03-22 20:26:08 +07:00
|
|
|
#include <linux/of_graph.h>
|
2014-05-23 16:48:28 +07:00
|
|
|
#include <sound/omap-hdmi-audio.h>
|
2017-08-02 15:54:07 +07:00
|
|
|
#include <media/cec.h>
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2016-05-27 18:40:49 +07:00
|
|
|
#include "omapdss.h"
|
2013-09-12 19:15:57 +07:00
|
|
|
#include "hdmi4_core.h"
|
2017-08-02 15:54:07 +07:00
|
|
|
#include "hdmi4_cec.h"
|
2011-03-12 13:34:27 +07:00
|
|
|
#include "dss.h"
|
2014-06-27 20:47:00 +07:00
|
|
|
#include "hdmi.h"
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2014-06-27 20:47:00 +07:00
|
|
|
static struct omap_hdmi hdmi;
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2011-05-27 14:52:19 +07:00
|
|
|
static int hdmi_runtime_get(void)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
DSSDBG("hdmi_runtime_get\n");
|
|
|
|
|
|
|
|
r = pm_runtime_get_sync(&hdmi.pdev->dev);
|
|
|
|
WARN_ON(r < 0);
|
2012-02-10 13:15:52 +07:00
|
|
|
if (r < 0)
|
2012-02-17 22:58:04 +07:00
|
|
|
return r;
|
2012-02-10 13:15:52 +07:00
|
|
|
|
|
|
|
return 0;
|
2011-05-27 14:52:19 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void hdmi_runtime_put(void)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
DSSDBG("hdmi_runtime_put\n");
|
|
|
|
|
2012-01-23 18:23:08 +07:00
|
|
|
r = pm_runtime_put_sync(&hdmi.pdev->dev);
|
2012-06-27 20:37:18 +07:00
|
|
|
WARN_ON(r < 0 && r != -ENOSYS);
|
2011-05-27 14:52:19 +07:00
|
|
|
}
|
|
|
|
|
2013-10-28 16:47:34 +07:00
|
|
|
static irqreturn_t hdmi_irq_handler(int irq, void *data)
|
|
|
|
{
|
2017-08-02 15:54:03 +07:00
|
|
|
struct omap_hdmi *hdmi = data;
|
|
|
|
struct hdmi_wp_data *wp = &hdmi->wp;
|
2013-10-28 16:47:34 +07:00
|
|
|
u32 irqstatus;
|
|
|
|
|
|
|
|
irqstatus = hdmi_wp_get_irqstatus(wp);
|
|
|
|
hdmi_wp_set_irqstatus(wp, irqstatus);
|
|
|
|
|
|
|
|
if ((irqstatus & HDMI_IRQ_LINK_CONNECT) &&
|
|
|
|
irqstatus & HDMI_IRQ_LINK_DISCONNECT) {
|
|
|
|
/*
|
|
|
|
* If we get both connect and disconnect interrupts at the same
|
|
|
|
* time, turn off the PHY, clear interrupts, and restart, which
|
|
|
|
* raises connect interrupt if a cable is connected, or nothing
|
|
|
|
* if cable is not connected.
|
|
|
|
*/
|
|
|
|
hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_OFF);
|
|
|
|
|
|
|
|
hdmi_wp_set_irqstatus(wp, HDMI_IRQ_LINK_CONNECT |
|
|
|
|
HDMI_IRQ_LINK_DISCONNECT);
|
|
|
|
|
|
|
|
hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON);
|
|
|
|
} else if (irqstatus & HDMI_IRQ_LINK_CONNECT) {
|
|
|
|
hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_TXON);
|
|
|
|
} else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) {
|
|
|
|
hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON);
|
|
|
|
}
|
2017-08-02 15:54:07 +07:00
|
|
|
if (irqstatus & HDMI_IRQ_CORE) {
|
|
|
|
u32 intr4 = hdmi_read_reg(hdmi->core.base, HDMI_CORE_SYS_INTR4);
|
|
|
|
|
|
|
|
hdmi_write_reg(hdmi->core.base, HDMI_CORE_SYS_INTR4, intr4);
|
|
|
|
if (intr4 & 8)
|
|
|
|
hdmi4_cec_irq(&hdmi->core);
|
|
|
|
}
|
2013-10-28 16:47:34 +07:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2013-05-10 19:20:52 +07:00
|
|
|
static int hdmi_init_regulator(void)
|
|
|
|
{
|
|
|
|
struct regulator *reg;
|
|
|
|
|
2014-06-27 20:47:00 +07:00
|
|
|
if (hdmi.vdda_reg != NULL)
|
2013-05-10 19:20:52 +07:00
|
|
|
return 0;
|
|
|
|
|
2013-06-10 18:05:10 +07:00
|
|
|
reg = devm_regulator_get(&hdmi.pdev->dev, "vdda");
|
2013-05-10 19:20:52 +07:00
|
|
|
|
|
|
|
if (IS_ERR(reg)) {
|
2013-12-19 21:15:34 +07:00
|
|
|
if (PTR_ERR(reg) != -EPROBE_DEFER)
|
2013-06-10 18:05:10 +07:00
|
|
|
DSSERR("can't get VDDA regulator\n");
|
2013-05-10 19:20:52 +07:00
|
|
|
return PTR_ERR(reg);
|
|
|
|
}
|
|
|
|
|
2014-06-27 20:47:00 +07:00
|
|
|
hdmi.vdda_reg = reg;
|
2013-05-10 19:20:52 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
static int hdmi_power_on_core(struct omap_dss_device *dssdev)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
2012-01-06 19:22:09 +07:00
|
|
|
int r;
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2017-08-02 15:54:05 +07:00
|
|
|
if (hdmi.core.core_pwr_cnt++)
|
|
|
|
return 0;
|
|
|
|
|
2014-06-27 20:47:00 +07:00
|
|
|
r = regulator_enable(hdmi.vdda_reg);
|
2012-08-15 19:55:04 +07:00
|
|
|
if (r)
|
2017-08-02 15:54:05 +07:00
|
|
|
goto err_reg_enable;
|
2012-08-15 19:55:04 +07:00
|
|
|
|
2011-05-27 14:52:19 +07:00
|
|
|
r = hdmi_runtime_get();
|
|
|
|
if (r)
|
2012-04-26 18:48:32 +07:00
|
|
|
goto err_runtime_get;
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2017-08-02 15:54:04 +07:00
|
|
|
hdmi4_core_powerdown_disable(&hdmi.core);
|
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
/* Make selection of HDMI in DSS */
|
|
|
|
dss_select_hdmi_venc_clk_source(DSS_HDMI_M_PCLK);
|
|
|
|
|
2013-05-24 17:20:17 +07:00
|
|
|
hdmi.core_enabled = true;
|
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_runtime_get:
|
2014-06-27 20:47:00 +07:00
|
|
|
regulator_disable(hdmi.vdda_reg);
|
2017-08-02 15:54:05 +07:00
|
|
|
err_reg_enable:
|
|
|
|
hdmi.core.core_pwr_cnt--;
|
2013-05-15 14:48:45 +07:00
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hdmi_power_off_core(struct omap_dss_device *dssdev)
|
|
|
|
{
|
2017-08-02 15:54:05 +07:00
|
|
|
if (--hdmi.core.core_pwr_cnt)
|
|
|
|
return;
|
|
|
|
|
2013-05-24 17:20:17 +07:00
|
|
|
hdmi.core_enabled = false;
|
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
hdmi_runtime_put();
|
2014-06-27 20:47:00 +07:00
|
|
|
regulator_disable(hdmi.vdda_reg);
|
2012-10-19 21:42:10 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_power_on_full(struct omap_dss_device *dssdev)
|
|
|
|
{
|
|
|
|
int r;
|
2016-09-22 18:07:04 +07:00
|
|
|
struct videomode *vm;
|
2015-11-05 14:56:21 +07:00
|
|
|
enum omap_channel channel = dssdev->dispc_channel;
|
2013-10-28 16:47:34 +07:00
|
|
|
struct hdmi_wp_data *wp = &hdmi.wp;
|
2014-10-22 19:02:17 +07:00
|
|
|
struct dss_pll_clock_info hdmi_cinfo = { 0 };
|
2016-01-13 23:41:33 +07:00
|
|
|
unsigned pc;
|
2012-10-19 21:42:10 +07:00
|
|
|
|
|
|
|
r = hdmi_power_on_core(dssdev);
|
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
2013-10-28 16:47:34 +07:00
|
|
|
/* disable and clear irqs */
|
2017-08-02 15:54:03 +07:00
|
|
|
hdmi_wp_clear_irqenable(wp, ~HDMI_IRQ_CORE);
|
|
|
|
hdmi_wp_set_irqstatus(wp, ~HDMI_IRQ_CORE);
|
2013-10-28 16:47:34 +07:00
|
|
|
|
2016-09-22 18:07:04 +07:00
|
|
|
vm = &hdmi.cfg.vm;
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2016-09-22 18:07:04 +07:00
|
|
|
DSSDBG("hdmi_power_on hactive= %d vactive = %d\n", vm->hactive,
|
|
|
|
vm->vactive);
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2016-09-22 18:07:04 +07:00
|
|
|
pc = vm->pixelclock;
|
|
|
|
if (vm->flags & DISPLAY_FLAGS_DOUBLECLK)
|
2016-01-13 23:41:33 +07:00
|
|
|
pc *= 2;
|
|
|
|
|
2016-05-18 15:15:21 +07:00
|
|
|
/* DSS_HDMI_TCLK is bitclk / 10 */
|
|
|
|
pc *= 10;
|
|
|
|
|
2016-05-18 14:45:20 +07:00
|
|
|
dss_pll_calc_b(&hdmi.pll.pll, clk_get_rate(hdmi.pll.pll.clkin),
|
|
|
|
pc, &hdmi_cinfo);
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2014-10-22 19:02:17 +07:00
|
|
|
r = dss_pll_enable(&hdmi.pll.pll);
|
2011-03-12 13:34:27 +07:00
|
|
|
if (r) {
|
2014-10-16 20:01:51 +07:00
|
|
|
DSSERR("Failed to enable PLL\n");
|
2012-04-26 18:48:32 +07:00
|
|
|
goto err_pll_enable;
|
2011-03-12 13:34:27 +07:00
|
|
|
}
|
|
|
|
|
2014-10-22 19:02:17 +07:00
|
|
|
r = dss_pll_set_config(&hdmi.pll.pll, &hdmi_cinfo);
|
2014-10-16 20:01:51 +07:00
|
|
|
if (r) {
|
|
|
|
DSSERR("Failed to configure PLL\n");
|
|
|
|
goto err_pll_cfg;
|
|
|
|
}
|
|
|
|
|
2014-10-22 19:02:17 +07:00
|
|
|
r = hdmi_phy_configure(&hdmi.phy, hdmi_cinfo.clkdco,
|
|
|
|
hdmi_cinfo.clkout[0]);
|
2011-03-12 13:34:27 +07:00
|
|
|
if (r) {
|
2013-10-28 16:47:34 +07:00
|
|
|
DSSDBG("Failed to configure PHY\n");
|
|
|
|
goto err_phy_cfg;
|
2011-03-12 13:34:27 +07:00
|
|
|
}
|
|
|
|
|
2013-10-28 16:47:34 +07:00
|
|
|
r = hdmi_wp_set_phy_pwr(wp, HDMI_PHYPWRCMD_LDOON);
|
|
|
|
if (r)
|
|
|
|
goto err_phy_pwr;
|
|
|
|
|
2013-10-08 15:52:03 +07:00
|
|
|
hdmi4_configure(&hdmi.core, &hdmi.wp, &hdmi.cfg);
|
2011-03-12 13:34:27 +07:00
|
|
|
|
|
|
|
/* tv size */
|
2016-09-22 18:07:04 +07:00
|
|
|
dss_mgr_set_timings(channel, vm);
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2015-11-05 14:56:21 +07:00
|
|
|
r = dss_mgr_enable(channel);
|
2011-11-21 18:42:58 +07:00
|
|
|
if (r)
|
|
|
|
goto err_mgr_enable;
|
2011-08-31 18:47:11 +07:00
|
|
|
|
2015-03-24 20:46:35 +07:00
|
|
|
r = hdmi_wp_video_start(&hdmi.wp);
|
|
|
|
if (r)
|
|
|
|
goto err_vid_enable;
|
|
|
|
|
2013-10-28 16:47:34 +07:00
|
|
|
hdmi_wp_set_irqenable(wp,
|
|
|
|
HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT);
|
|
|
|
|
2011-03-12 13:34:27 +07:00
|
|
|
return 0;
|
2011-11-21 18:42:58 +07:00
|
|
|
|
2012-04-28 01:48:45 +07:00
|
|
|
err_vid_enable:
|
2015-11-05 14:56:21 +07:00
|
|
|
dss_mgr_disable(channel);
|
2015-03-24 20:46:35 +07:00
|
|
|
err_mgr_enable:
|
2013-10-28 16:47:34 +07:00
|
|
|
hdmi_wp_set_phy_pwr(&hdmi.wp, HDMI_PHYPWRCMD_OFF);
|
|
|
|
err_phy_pwr:
|
2015-03-24 20:46:33 +07:00
|
|
|
err_phy_cfg:
|
2014-10-16 20:01:51 +07:00
|
|
|
err_pll_cfg:
|
2014-10-22 19:02:17 +07:00
|
|
|
dss_pll_disable(&hdmi.pll.pll);
|
2012-04-26 18:48:32 +07:00
|
|
|
err_pll_enable:
|
2012-10-19 21:42:10 +07:00
|
|
|
hdmi_power_off_core(dssdev);
|
2011-03-12 13:34:27 +07:00
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
static void hdmi_power_off_full(struct omap_dss_device *dssdev)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
2015-11-05 14:56:21 +07:00
|
|
|
enum omap_channel channel = dssdev->dispc_channel;
|
2012-09-07 19:26:20 +07:00
|
|
|
|
2017-08-02 15:54:03 +07:00
|
|
|
hdmi_wp_clear_irqenable(&hdmi.wp, ~HDMI_IRQ_CORE);
|
2013-10-28 16:47:34 +07:00
|
|
|
|
2013-10-08 15:52:03 +07:00
|
|
|
hdmi_wp_video_stop(&hdmi.wp);
|
2013-10-28 16:47:34 +07:00
|
|
|
|
2015-11-05 14:56:21 +07:00
|
|
|
dss_mgr_disable(channel);
|
2015-03-24 20:46:35 +07:00
|
|
|
|
2013-10-28 16:47:34 +07:00
|
|
|
hdmi_wp_set_phy_pwr(&hdmi.wp, HDMI_PHYPWRCMD_OFF);
|
|
|
|
|
2014-10-22 19:02:17 +07:00
|
|
|
dss_pll_disable(&hdmi.pll.pll);
|
2012-08-15 19:55:04 +07:00
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
hdmi_power_off_core(dssdev);
|
2011-03-12 13:34:27 +07:00
|
|
|
}
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
static int hdmi_display_check_timing(struct omap_dss_device *dssdev,
|
2016-09-22 18:07:04 +07:00
|
|
|
struct videomode *vm)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
2016-09-22 18:07:04 +07:00
|
|
|
if (!dispc_mgr_timings_ok(dssdev->dispc_channel, vm))
|
2011-03-12 13:34:27 +07:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
static void hdmi_display_set_timing(struct omap_dss_device *dssdev,
|
2016-09-22 18:07:04 +07:00
|
|
|
struct videomode *vm)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
2012-08-15 02:10:31 +07:00
|
|
|
mutex_lock(&hdmi.lock);
|
|
|
|
|
2016-09-22 18:07:04 +07:00
|
|
|
hdmi.cfg.vm = *vm;
|
2013-05-16 14:44:13 +07:00
|
|
|
|
2016-09-22 18:07:04 +07:00
|
|
|
dispc_set_tv_pclk(vm->pixelclock);
|
2013-12-09 21:09:08 +07:00
|
|
|
|
2012-08-15 02:10:31 +07:00
|
|
|
mutex_unlock(&hdmi.lock);
|
2011-03-12 13:34:27 +07:00
|
|
|
}
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
static void hdmi_display_get_timings(struct omap_dss_device *dssdev,
|
2016-09-22 18:07:04 +07:00
|
|
|
struct videomode *vm)
|
2013-05-24 17:20:17 +07:00
|
|
|
{
|
2016-09-22 18:07:04 +07:00
|
|
|
*vm = hdmi.cfg.vm;
|
2013-05-24 17:20:17 +07:00
|
|
|
}
|
|
|
|
|
2012-03-02 23:01:07 +07:00
|
|
|
static void hdmi_dump_regs(struct seq_file *s)
|
2011-09-22 15:07:45 +07:00
|
|
|
{
|
|
|
|
mutex_lock(&hdmi.lock);
|
|
|
|
|
2012-10-21 19:54:26 +07:00
|
|
|
if (hdmi_runtime_get()) {
|
|
|
|
mutex_unlock(&hdmi.lock);
|
2011-09-22 15:07:45 +07:00
|
|
|
return;
|
2012-10-21 19:54:26 +07:00
|
|
|
}
|
2011-09-22 15:07:45 +07:00
|
|
|
|
2013-10-08 15:52:03 +07:00
|
|
|
hdmi_wp_dump(&hdmi.wp, s);
|
|
|
|
hdmi_pll_dump(&hdmi.pll, s);
|
|
|
|
hdmi_phy_dump(&hdmi.phy, s);
|
|
|
|
hdmi4_core_dump(&hdmi.core, s);
|
2011-09-22 15:07:45 +07:00
|
|
|
|
|
|
|
hdmi_runtime_put();
|
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
}
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
static int read_edid(u8 *buf, int len)
|
2011-08-25 21:12:56 +07:00
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
mutex_lock(&hdmi.lock);
|
|
|
|
|
|
|
|
r = hdmi_runtime_get();
|
|
|
|
BUG_ON(r);
|
|
|
|
|
2013-10-08 15:52:03 +07:00
|
|
|
r = hdmi4_read_edid(&hdmi.core, buf, len);
|
2011-08-25 21:12:56 +07:00
|
|
|
|
|
|
|
hdmi_runtime_put();
|
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2015-08-28 21:21:46 +07:00
|
|
|
static void hdmi_start_audio_stream(struct omap_hdmi *hd)
|
|
|
|
{
|
|
|
|
hdmi_wp_audio_enable(&hd->wp, true);
|
|
|
|
hdmi4_audio_start(&hd->core, &hd->wp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hdmi_stop_audio_stream(struct omap_hdmi *hd)
|
|
|
|
{
|
|
|
|
hdmi4_audio_stop(&hd->core, &hd->wp);
|
|
|
|
hdmi_wp_audio_enable(&hd->wp, false);
|
|
|
|
}
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
static int hdmi_display_enable(struct omap_dss_device *dssdev)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
OMAPDSS: combine omap_dss_output into omap_dss_device
We currently have omap_dss_device, which represents an external display
device, sometimes an external encoder, sometimes a panel. Then we have
omap_dss_output, which represents DSS's output encoder.
In the future with new display device model, we construct a video
pipeline from the display blocks. To accomplish this, all the blocks
need to be presented by the same entity.
Thus, this patch combines omap_dss_output into omap_dss_device. Some of
the fields in omap_dss_output are already found in omap_dss_device, but
some are not. This means we'll have DSS output specific fields in
omap_dss_device, which is not very nice. However, it is easier to just
keep those output specific fields there for now, and after transition to
new display device model is made, they can be cleaned up easier than
could be done now.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
2013-04-19 19:09:34 +07:00
|
|
|
struct omap_dss_device *out = &hdmi.output;
|
2015-08-28 21:21:46 +07:00
|
|
|
unsigned long flags;
|
2011-03-12 13:34:27 +07:00
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
DSSDBG("ENTER hdmi_display_enable\n");
|
|
|
|
|
|
|
|
mutex_lock(&hdmi.lock);
|
|
|
|
|
2015-11-05 14:34:51 +07:00
|
|
|
if (!out->dispc_channel_connected) {
|
2012-09-07 19:26:20 +07:00
|
|
|
DSSERR("failed to enable display: no output/manager\n");
|
2011-06-23 20:38:21 +07:00
|
|
|
r = -ENODEV;
|
|
|
|
goto err0;
|
|
|
|
}
|
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
r = hdmi_power_on_full(dssdev);
|
2011-03-12 13:34:27 +07:00
|
|
|
if (r) {
|
|
|
|
DSSERR("failed to power on device\n");
|
2013-04-25 17:12:07 +07:00
|
|
|
goto err0;
|
2011-03-12 13:34:27 +07:00
|
|
|
}
|
|
|
|
|
2015-08-28 21:21:46 +07:00
|
|
|
if (hdmi.audio_configured) {
|
|
|
|
r = hdmi4_audio_config(&hdmi.core, &hdmi.wp, &hdmi.audio_config,
|
2016-09-22 18:07:04 +07:00
|
|
|
hdmi.cfg.vm.pixelclock);
|
2015-08-28 21:21:46 +07:00
|
|
|
if (r) {
|
|
|
|
DSSERR("Error restoring audio configuration: %d", r);
|
|
|
|
hdmi.audio_abort_cb(&hdmi.pdev->dev);
|
|
|
|
hdmi.audio_configured = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irqsave(&hdmi.audio_playing_lock, flags);
|
|
|
|
if (hdmi.audio_configured && hdmi.audio_playing)
|
|
|
|
hdmi_start_audio_stream(&hdmi);
|
2014-05-23 16:48:28 +07:00
|
|
|
hdmi.display_enabled = true;
|
2015-08-28 21:21:46 +07:00
|
|
|
spin_unlock_irqrestore(&hdmi.audio_playing_lock, flags);
|
2014-05-23 16:48:28 +07:00
|
|
|
|
2011-03-12 13:34:27 +07:00
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err0:
|
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
static void hdmi_display_disable(struct omap_dss_device *dssdev)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
2015-08-28 21:21:46 +07:00
|
|
|
unsigned long flags;
|
|
|
|
|
2011-03-12 13:34:27 +07:00
|
|
|
DSSDBG("Enter hdmi_display_disable\n");
|
|
|
|
|
|
|
|
mutex_lock(&hdmi.lock);
|
|
|
|
|
2015-08-28 21:21:46 +07:00
|
|
|
spin_lock_irqsave(&hdmi.audio_playing_lock, flags);
|
|
|
|
hdmi_stop_audio_stream(&hdmi);
|
|
|
|
hdmi.display_enabled = false;
|
|
|
|
spin_unlock_irqrestore(&hdmi.audio_playing_lock, flags);
|
2014-05-23 16:48:28 +07:00
|
|
|
|
2012-10-19 21:42:10 +07:00
|
|
|
hdmi_power_off_full(dssdev);
|
2011-03-12 13:34:27 +07:00
|
|
|
|
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
}
|
|
|
|
|
2017-08-02 15:54:02 +07:00
|
|
|
int hdmi4_core_enable(struct omap_dss_device *dssdev)
|
2012-10-19 21:42:27 +07:00
|
|
|
{
|
|
|
|
int r = 0;
|
|
|
|
|
2017-08-02 15:54:02 +07:00
|
|
|
DSSDBG("ENTER omapdss_hdmi4_core_enable\n");
|
2012-10-19 21:42:27 +07:00
|
|
|
|
|
|
|
mutex_lock(&hdmi.lock);
|
|
|
|
|
|
|
|
r = hdmi_power_on_core(dssdev);
|
|
|
|
if (r) {
|
|
|
|
DSSERR("failed to power on device\n");
|
|
|
|
goto err0;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err0:
|
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2017-08-02 15:54:02 +07:00
|
|
|
void hdmi4_core_disable(struct omap_dss_device *dssdev)
|
2012-10-19 21:42:27 +07:00
|
|
|
{
|
2017-08-02 15:54:02 +07:00
|
|
|
DSSDBG("Enter omapdss_hdmi4_core_disable\n");
|
2012-10-19 21:42:27 +07:00
|
|
|
|
|
|
|
mutex_lock(&hdmi.lock);
|
|
|
|
|
|
|
|
hdmi_power_off_core(dssdev);
|
|
|
|
|
|
|
|
mutex_unlock(&hdmi.lock);
|
|
|
|
}
|
|
|
|
|
2013-05-24 17:20:17 +07:00
|
|
|
static int hdmi_connect(struct omap_dss_device *dssdev,
|
|
|
|
struct omap_dss_device *dst)
|
|
|
|
{
|
2015-11-05 14:56:21 +07:00
|
|
|
enum omap_channel channel = dssdev->dispc_channel;
|
2013-05-24 17:20:17 +07:00
|
|
|
int r;
|
|
|
|
|
|
|
|
r = hdmi_init_regulator();
|
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
2015-11-05 14:56:21 +07:00
|
|
|
r = dss_mgr_connect(channel, dssdev);
|
2013-05-24 17:20:17 +07:00
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = omapdss_output_set_device(dssdev, dst);
|
|
|
|
if (r) {
|
|
|
|
DSSERR("failed to connect output to new device: %s\n",
|
|
|
|
dst->name);
|
2015-11-05 14:56:21 +07:00
|
|
|
dss_mgr_disconnect(channel, dssdev);
|
2013-05-24 17:20:17 +07:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hdmi_disconnect(struct omap_dss_device *dssdev,
|
|
|
|
struct omap_dss_device *dst)
|
|
|
|
{
|
2015-11-05 14:56:21 +07:00
|
|
|
enum omap_channel channel = dssdev->dispc_channel;
|
|
|
|
|
2013-07-24 17:06:54 +07:00
|
|
|
WARN_ON(dst != dssdev->dst);
|
2013-05-24 17:20:17 +07:00
|
|
|
|
2013-07-24 17:06:54 +07:00
|
|
|
if (dst != dssdev->dst)
|
2013-05-24 17:20:17 +07:00
|
|
|
return;
|
|
|
|
|
|
|
|
omapdss_output_unset_device(dssdev);
|
|
|
|
|
2015-11-05 14:56:21 +07:00
|
|
|
dss_mgr_disconnect(channel, dssdev);
|
2013-05-24 17:20:17 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_read_edid(struct omap_dss_device *dssdev,
|
|
|
|
u8 *edid, int len)
|
|
|
|
{
|
|
|
|
bool need_enable;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
need_enable = hdmi.core_enabled == false;
|
|
|
|
|
|
|
|
if (need_enable) {
|
2017-08-02 15:54:02 +07:00
|
|
|
r = hdmi4_core_enable(dssdev);
|
2013-05-24 17:20:17 +07:00
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
r = read_edid(edid, len);
|
2017-08-02 15:54:07 +07:00
|
|
|
if (r >= 256)
|
|
|
|
hdmi4_cec_set_phys_addr(&hdmi.core,
|
|
|
|
cec_get_edid_phys_addr(edid, r, NULL));
|
|
|
|
else
|
|
|
|
hdmi4_cec_set_phys_addr(&hdmi.core, CEC_PHYS_ADDR_INVALID);
|
2013-05-24 17:20:17 +07:00
|
|
|
if (need_enable)
|
2017-08-02 15:54:02 +07:00
|
|
|
hdmi4_core_disable(dssdev);
|
2013-05-24 17:20:17 +07:00
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2017-08-17 20:19:57 +07:00
|
|
|
static void hdmi_lost_hotplug(struct omap_dss_device *dssdev)
|
|
|
|
{
|
|
|
|
hdmi4_cec_set_phys_addr(&hdmi.core, CEC_PHYS_ADDR_INVALID);
|
|
|
|
}
|
|
|
|
|
2014-06-18 18:21:44 +07:00
|
|
|
static int hdmi_set_infoframe(struct omap_dss_device *dssdev,
|
|
|
|
const struct hdmi_avi_infoframe *avi)
|
|
|
|
{
|
|
|
|
hdmi.cfg.infoframe = *avi;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_set_hdmi_mode(struct omap_dss_device *dssdev,
|
|
|
|
bool hdmi_mode)
|
|
|
|
{
|
|
|
|
hdmi.cfg.hdmi_dvi_mode = hdmi_mode ? HDMI_HDMI : HDMI_DVI;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-05-24 17:20:17 +07:00
|
|
|
static const struct omapdss_hdmi_ops hdmi_ops = {
|
|
|
|
.connect = hdmi_connect,
|
|
|
|
.disconnect = hdmi_disconnect,
|
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
.enable = hdmi_display_enable,
|
|
|
|
.disable = hdmi_display_disable,
|
2013-05-24 17:20:17 +07:00
|
|
|
|
2013-05-15 14:48:45 +07:00
|
|
|
.check_timings = hdmi_display_check_timing,
|
|
|
|
.set_timings = hdmi_display_set_timing,
|
|
|
|
.get_timings = hdmi_display_get_timings,
|
2013-05-24 17:20:17 +07:00
|
|
|
|
|
|
|
.read_edid = hdmi_read_edid,
|
2017-08-17 20:19:57 +07:00
|
|
|
.lost_hotplug = hdmi_lost_hotplug,
|
2014-06-18 18:21:44 +07:00
|
|
|
.set_infoframe = hdmi_set_infoframe,
|
|
|
|
.set_hdmi_mode = hdmi_set_hdmi_mode,
|
2013-05-24 17:20:17 +07:00
|
|
|
};
|
|
|
|
|
2013-04-26 18:48:43 +07:00
|
|
|
static void hdmi_init_output(struct platform_device *pdev)
|
2012-09-26 18:00:49 +07:00
|
|
|
{
|
OMAPDSS: combine omap_dss_output into omap_dss_device
We currently have omap_dss_device, which represents an external display
device, sometimes an external encoder, sometimes a panel. Then we have
omap_dss_output, which represents DSS's output encoder.
In the future with new display device model, we construct a video
pipeline from the display blocks. To accomplish this, all the blocks
need to be presented by the same entity.
Thus, this patch combines omap_dss_output into omap_dss_device. Some of
the fields in omap_dss_output are already found in omap_dss_device, but
some are not. This means we'll have DSS output specific fields in
omap_dss_device, which is not very nice. However, it is easier to just
keep those output specific fields there for now, and after transition to
new display device model is made, they can be cleaned up easier than
could be done now.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
2013-04-19 19:09:34 +07:00
|
|
|
struct omap_dss_device *out = &hdmi.output;
|
2012-09-26 18:00:49 +07:00
|
|
|
|
OMAPDSS: combine omap_dss_output into omap_dss_device
We currently have omap_dss_device, which represents an external display
device, sometimes an external encoder, sometimes a panel. Then we have
omap_dss_output, which represents DSS's output encoder.
In the future with new display device model, we construct a video
pipeline from the display blocks. To accomplish this, all the blocks
need to be presented by the same entity.
Thus, this patch combines omap_dss_output into omap_dss_device. Some of
the fields in omap_dss_output are already found in omap_dss_device, but
some are not. This means we'll have DSS output specific fields in
omap_dss_device, which is not very nice. However, it is easier to just
keep those output specific fields there for now, and after transition to
new display device model is made, they can be cleaned up easier than
could be done now.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
2013-04-19 19:09:34 +07:00
|
|
|
out->dev = &pdev->dev;
|
2012-09-26 18:00:49 +07:00
|
|
|
out->id = OMAP_DSS_OUTPUT_HDMI;
|
OMAPDSS: combine omap_dss_output into omap_dss_device
We currently have omap_dss_device, which represents an external display
device, sometimes an external encoder, sometimes a panel. Then we have
omap_dss_output, which represents DSS's output encoder.
In the future with new display device model, we construct a video
pipeline from the display blocks. To accomplish this, all the blocks
need to be presented by the same entity.
Thus, this patch combines omap_dss_output into omap_dss_device. Some of
the fields in omap_dss_output are already found in omap_dss_device, but
some are not. This means we'll have DSS output specific fields in
omap_dss_device, which is not very nice. However, it is easier to just
keep those output specific fields there for now, and after transition to
new display device model is made, they can be cleaned up easier than
could be done now.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
2013-04-19 19:09:34 +07:00
|
|
|
out->output_type = OMAP_DISPLAY_TYPE_HDMI;
|
2013-02-18 18:06:01 +07:00
|
|
|
out->name = "hdmi.0";
|
2013-02-13 16:23:54 +07:00
|
|
|
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
|
2013-05-24 17:20:17 +07:00
|
|
|
out->ops.hdmi = &hdmi_ops;
|
2013-05-03 15:42:18 +07:00
|
|
|
out->owner = THIS_MODULE;
|
2012-09-26 18:00:49 +07:00
|
|
|
|
2013-04-24 17:32:51 +07:00
|
|
|
omapdss_register_output(out);
|
2012-09-26 18:00:49 +07:00
|
|
|
}
|
|
|
|
|
2014-12-02 19:12:56 +07:00
|
|
|
static void hdmi_uninit_output(struct platform_device *pdev)
|
2012-09-26 18:00:49 +07:00
|
|
|
{
|
OMAPDSS: combine omap_dss_output into omap_dss_device
We currently have omap_dss_device, which represents an external display
device, sometimes an external encoder, sometimes a panel. Then we have
omap_dss_output, which represents DSS's output encoder.
In the future with new display device model, we construct a video
pipeline from the display blocks. To accomplish this, all the blocks
need to be presented by the same entity.
Thus, this patch combines omap_dss_output into omap_dss_device. Some of
the fields in omap_dss_output are already found in omap_dss_device, but
some are not. This means we'll have DSS output specific fields in
omap_dss_device, which is not very nice. However, it is easier to just
keep those output specific fields there for now, and after transition to
new display device model is made, they can be cleaned up easier than
could be done now.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
2013-04-19 19:09:34 +07:00
|
|
|
struct omap_dss_device *out = &hdmi.output;
|
2012-09-26 18:00:49 +07:00
|
|
|
|
2013-04-24 17:32:51 +07:00
|
|
|
omapdss_unregister_output(out);
|
2012-09-26 18:00:49 +07:00
|
|
|
}
|
|
|
|
|
2014-04-17 16:54:02 +07:00
|
|
|
static int hdmi_probe_of(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct device_node *node = pdev->dev.of_node;
|
|
|
|
struct device_node *ep;
|
|
|
|
int r;
|
|
|
|
|
2017-03-22 20:26:08 +07:00
|
|
|
ep = of_graph_get_endpoint_by_regs(node, 0, 0);
|
2014-04-17 16:54:02 +07:00
|
|
|
if (!ep)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
r = hdmi_parse_lanes_of(pdev, ep, &hdmi.phy);
|
|
|
|
if (r)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
of_node_put(ep);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
of_node_put(ep);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2014-05-23 16:48:28 +07:00
|
|
|
/* Audio callbacks */
|
|
|
|
static int hdmi_audio_startup(struct device *dev,
|
|
|
|
void (*abort_cb)(struct device *dev))
|
|
|
|
{
|
|
|
|
struct omap_hdmi *hd = dev_get_drvdata(dev);
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&hd->lock);
|
|
|
|
|
|
|
|
if (!hdmi_mode_has_audio(&hd->cfg) || !hd->display_enabled) {
|
|
|
|
ret = -EPERM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
hd->audio_abort_cb = abort_cb;
|
|
|
|
|
|
|
|
out:
|
|
|
|
mutex_unlock(&hd->lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_audio_shutdown(struct device *dev)
|
|
|
|
{
|
|
|
|
struct omap_hdmi *hd = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
mutex_lock(&hd->lock);
|
|
|
|
hd->audio_abort_cb = NULL;
|
2015-08-28 21:21:46 +07:00
|
|
|
hd->audio_configured = false;
|
|
|
|
hd->audio_playing = false;
|
2014-05-23 16:48:28 +07:00
|
|
|
mutex_unlock(&hd->lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_audio_start(struct device *dev)
|
|
|
|
{
|
|
|
|
struct omap_hdmi *hd = dev_get_drvdata(dev);
|
2015-08-28 21:21:46 +07:00
|
|
|
unsigned long flags;
|
2014-05-23 16:48:28 +07:00
|
|
|
|
|
|
|
WARN_ON(!hdmi_mode_has_audio(&hd->cfg));
|
|
|
|
|
2015-08-28 21:21:46 +07:00
|
|
|
spin_lock_irqsave(&hd->audio_playing_lock, flags);
|
|
|
|
|
|
|
|
if (hd->display_enabled)
|
|
|
|
hdmi_start_audio_stream(hd);
|
|
|
|
hd->audio_playing = true;
|
2014-05-23 16:48:28 +07:00
|
|
|
|
2015-08-28 21:21:46 +07:00
|
|
|
spin_unlock_irqrestore(&hd->audio_playing_lock, flags);
|
2014-05-23 16:48:28 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hdmi_audio_stop(struct device *dev)
|
|
|
|
{
|
|
|
|
struct omap_hdmi *hd = dev_get_drvdata(dev);
|
2015-08-28 21:21:46 +07:00
|
|
|
unsigned long flags;
|
2014-05-23 16:48:28 +07:00
|
|
|
|
|
|
|
WARN_ON(!hdmi_mode_has_audio(&hd->cfg));
|
|
|
|
|
2015-08-28 21:21:46 +07:00
|
|
|
spin_lock_irqsave(&hd->audio_playing_lock, flags);
|
|
|
|
|
|
|
|
if (hd->display_enabled)
|
|
|
|
hdmi_stop_audio_stream(hd);
|
|
|
|
hd->audio_playing = false;
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&hd->audio_playing_lock, flags);
|
2014-05-23 16:48:28 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_audio_config(struct device *dev,
|
|
|
|
struct omap_dss_audio *dss_audio)
|
|
|
|
{
|
|
|
|
struct omap_hdmi *hd = dev_get_drvdata(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_lock(&hd->lock);
|
|
|
|
|
|
|
|
if (!hdmi_mode_has_audio(&hd->cfg) || !hd->display_enabled) {
|
|
|
|
ret = -EPERM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = hdmi4_audio_config(&hd->core, &hd->wp, dss_audio,
|
2016-09-22 18:07:04 +07:00
|
|
|
hd->cfg.vm.pixelclock);
|
2015-08-28 21:21:46 +07:00
|
|
|
if (!ret) {
|
|
|
|
hd->audio_configured = true;
|
|
|
|
hd->audio_config = *dss_audio;
|
|
|
|
}
|
2014-05-23 16:48:28 +07:00
|
|
|
out:
|
|
|
|
mutex_unlock(&hd->lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct omap_hdmi_audio_ops hdmi_audio_ops = {
|
|
|
|
.audio_startup = hdmi_audio_startup,
|
|
|
|
.audio_shutdown = hdmi_audio_shutdown,
|
|
|
|
.audio_start = hdmi_audio_start,
|
|
|
|
.audio_stop = hdmi_audio_stop,
|
|
|
|
.audio_config = hdmi_audio_config,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int hdmi_audio_register(struct device *dev)
|
|
|
|
{
|
|
|
|
struct omap_hdmi_audio_pdata pdata = {
|
|
|
|
.dev = dev,
|
2017-08-11 20:49:04 +07:00
|
|
|
.version = 4,
|
2014-05-23 16:48:28 +07:00
|
|
|
.audio_dma_addr = hdmi_wp_get_audio_dma_addr(&hdmi.wp),
|
|
|
|
.ops = &hdmi_audio_ops,
|
|
|
|
};
|
|
|
|
|
|
|
|
hdmi.audio_pdev = platform_device_register_data(
|
|
|
|
dev, "omap-hdmi-audio", PLATFORM_DEVID_AUTO,
|
|
|
|
&pdata, sizeof(pdata));
|
|
|
|
|
|
|
|
if (IS_ERR(hdmi.audio_pdev))
|
|
|
|
return PTR_ERR(hdmi.audio_pdev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-12 13:34:27 +07:00
|
|
|
/* HDMI HW IP initialisation */
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
static int hdmi4_bind(struct device *dev, struct device *master, void *data)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
2012-05-02 18:55:12 +07:00
|
|
|
int r;
|
2013-10-28 16:47:34 +07:00
|
|
|
int irq;
|
2011-03-12 13:34:27 +07:00
|
|
|
|
|
|
|
hdmi.pdev = pdev;
|
2014-06-27 20:47:00 +07:00
|
|
|
dev_set_drvdata(&pdev->dev, &hdmi);
|
2011-03-12 13:34:27 +07:00
|
|
|
|
|
|
|
mutex_init(&hdmi.lock);
|
2015-08-28 21:21:46 +07:00
|
|
|
spin_lock_init(&hdmi.audio_playing_lock);
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2017-05-07 04:42:26 +07:00
|
|
|
r = hdmi_probe_of(pdev);
|
|
|
|
if (r)
|
|
|
|
return r;
|
2014-04-17 16:54:02 +07:00
|
|
|
|
2017-08-11 20:49:03 +07:00
|
|
|
r = hdmi_wp_init(pdev, &hdmi.wp, 4);
|
2013-08-06 16:26:55 +07:00
|
|
|
if (r)
|
|
|
|
return r;
|
2011-03-12 13:34:27 +07:00
|
|
|
|
2014-10-16 19:31:38 +07:00
|
|
|
r = hdmi_pll_init(pdev, &hdmi.pll, &hdmi.wp);
|
2013-10-08 14:25:26 +07:00
|
|
|
if (r)
|
|
|
|
return r;
|
|
|
|
|
2017-08-11 20:49:06 +07:00
|
|
|
r = hdmi_phy_init(pdev, &hdmi.phy, 4);
|
2013-10-08 14:37:00 +07:00
|
|
|
if (r)
|
2014-10-22 19:02:17 +07:00
|
|
|
goto err;
|
OMAPDSS: HDMI: clean up PHY power handling
The TRM tells to set PHY to TXON only after getting LINK_CONNECT, and to
set PHY to OFF or LDOON after getting LINK_DISCONNECT, in order to avoid
damage to the PHY.
We don't currently do it quite like that. Instead of using the HDMI
interrupts, we use HPD signal. This works, but is not actually quite
correct, as HPD comes at a different time than LINK_CONNECT and
LINK_DISCONNECT interrupts. Also, the HPD GPIO is a property of the TPD
level shifter, not HDMI IP, so handling the GPIO in the HDMI driver is
wrong.
This patch implements the PHY power handling correctly, using the
interrupts.
There is a corner case that causes some additional difficulties: we may
get both LINK_CONNECT and LINK_DISCONNECT interrupts at the same time.
This is handled in the code by retrying: turning off the PHY, clearing
the interrupt status, and re-enabling the PHY. This causes a new
LINK_CONNECT interrupt to happen if a cable is connected.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
2013-06-06 17:08:35 +07:00
|
|
|
|
2013-10-08 15:52:03 +07:00
|
|
|
r = hdmi4_core_init(pdev, &hdmi.core);
|
2013-10-08 15:46:05 +07:00
|
|
|
if (r)
|
2014-10-22 19:02:17 +07:00
|
|
|
goto err;
|
2011-05-27 14:52:19 +07:00
|
|
|
|
2017-08-02 15:54:07 +07:00
|
|
|
r = hdmi4_cec_init(pdev, &hdmi.core, &hdmi.wp);
|
|
|
|
if (r)
|
|
|
|
goto err;
|
|
|
|
|
2013-10-28 16:47:34 +07:00
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
|
|
if (irq < 0) {
|
|
|
|
DSSERR("platform_get_irq failed\n");
|
2014-10-22 19:02:17 +07:00
|
|
|
r = -ENODEV;
|
|
|
|
goto err;
|
2013-10-28 16:47:34 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
r = devm_request_threaded_irq(&pdev->dev, irq,
|
|
|
|
NULL, hdmi_irq_handler,
|
2017-08-02 15:54:03 +07:00
|
|
|
IRQF_ONESHOT, "OMAP HDMI", &hdmi);
|
2013-10-28 16:47:34 +07:00
|
|
|
if (r) {
|
|
|
|
DSSERR("HDMI IRQ request failed\n");
|
2014-10-22 19:02:17 +07:00
|
|
|
goto err;
|
2013-10-28 16:47:34 +07:00
|
|
|
}
|
|
|
|
|
2011-05-27 14:52:19 +07:00
|
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
|
2013-02-13 17:17:43 +07:00
|
|
|
hdmi_init_output(pdev);
|
|
|
|
|
2014-05-23 16:48:28 +07:00
|
|
|
r = hdmi_audio_register(&pdev->dev);
|
|
|
|
if (r) {
|
|
|
|
DSSERR("Registering HDMI audio failed\n");
|
|
|
|
hdmi_uninit_output(pdev);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2012-03-02 23:01:07 +07:00
|
|
|
dss_debugfs_create_file("hdmi", hdmi_dump_regs);
|
|
|
|
|
2012-04-26 18:48:32 +07:00
|
|
|
return 0;
|
2014-10-22 19:02:17 +07:00
|
|
|
err:
|
|
|
|
hdmi_pll_uninit(&hdmi.pll);
|
|
|
|
return r;
|
2012-04-26 18:48:32 +07:00
|
|
|
}
|
|
|
|
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
static void hdmi4_unbind(struct device *dev, struct device *master, void *data)
|
2011-03-12 13:34:27 +07:00
|
|
|
{
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
|
2014-05-23 16:48:28 +07:00
|
|
|
if (hdmi.audio_pdev)
|
|
|
|
platform_device_unregister(hdmi.audio_pdev);
|
|
|
|
|
2012-09-26 18:00:49 +07:00
|
|
|
hdmi_uninit_output(pdev);
|
|
|
|
|
2017-08-02 15:54:07 +07:00
|
|
|
hdmi4_cec_uninit(&hdmi.core);
|
|
|
|
|
2014-10-22 19:02:17 +07:00
|
|
|
hdmi_pll_uninit(&hdmi.pll);
|
|
|
|
|
2011-05-27 14:52:19 +07:00
|
|
|
pm_runtime_disable(&pdev->dev);
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct component_ops hdmi4_component_ops = {
|
|
|
|
.bind = hdmi4_bind,
|
|
|
|
.unbind = hdmi4_unbind,
|
|
|
|
};
|
2011-05-27 14:52:19 +07:00
|
|
|
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
static int hdmi4_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
return component_add(&pdev->dev, &hdmi4_component_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi4_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
component_del(&pdev->dev, &hdmi4_component_ops);
|
2011-03-12 13:34:27 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-27 14:52:19 +07:00
|
|
|
static int hdmi_runtime_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
dispc_runtime_put();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_runtime_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
r = dispc_runtime_get();
|
|
|
|
if (r < 0)
|
2012-02-17 22:58:04 +07:00
|
|
|
return r;
|
2011-05-27 14:52:19 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dev_pm_ops hdmi_pm_ops = {
|
|
|
|
.runtime_suspend = hdmi_runtime_suspend,
|
|
|
|
.runtime_resume = hdmi_runtime_resume,
|
|
|
|
};
|
|
|
|
|
2013-12-16 20:14:04 +07:00
|
|
|
static const struct of_device_id hdmi_of_match[] = {
|
|
|
|
{ .compatible = "ti,omap4-hdmi", },
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
|
2017-12-06 03:29:32 +07:00
|
|
|
struct platform_driver omapdss_hdmi4hw_driver = {
|
OMAPDSS: componentize omapdss
omapdss kernel module contains drivers for multiple devices, one for
each DSS submodule. The probing we have at the moment is a mess, and
doesn't give us proper deferred probing nor ensure that all the devices
are probed before omapfb/omapdrm start using omapdss.
This patch solves the mess by using the component system for DSS
submodules.
The changes to all DSS submodules (dispc, dpi, dsi, hdmi4/5, rfbi, sdi,
venc) are the same: probe & remove functions are changed to bind &
unbind, and new probe & remove functions are added which call
component_add/del.
The dss_core driver (dss.c) acts as a component master. Adding and
matching the components is simple: all dss device's child devices are
added as components.
However, we do have some dependencies between the drivers. The order in
which they should be probed is reflected by the list in core.c
(dss_output_drv_reg_funcs). The drivers are registered in that order,
which causes the components to be added in that order, which makes the
components to be bound in that order. This feels a bit fragile, and we
probably should improve the code to manage binds in random order.
However, for now, this works fine.
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
2015-06-04 19:22:23 +07:00
|
|
|
.probe = hdmi4_probe,
|
|
|
|
.remove = hdmi4_remove,
|
2011-03-12 13:34:27 +07:00
|
|
|
.driver = {
|
|
|
|
.name = "omapdss_hdmi",
|
2011-05-27 14:52:19 +07:00
|
|
|
.pm = &hdmi_pm_ops,
|
2013-12-16 20:14:04 +07:00
|
|
|
.of_match_table = hdmi_of_match,
|
2014-10-16 13:54:25 +07:00
|
|
|
.suppress_bind_attrs = true,
|
2011-03-12 13:34:27 +07:00
|
|
|
},
|
|
|
|
};
|