2017-05-28 00:09:35 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2016 Maxime Ripard
|
|
|
|
*
|
|
|
|
* Maxime Ripard <maxime.ripard@free-electrons.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License as
|
|
|
|
* published by the Free Software Foundation; either version 2 of
|
|
|
|
* the License, or (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <drm/drmP.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
|
|
#include <drm/drm_crtc_helper.h>
|
|
|
|
#include <drm/drm_edid.h>
|
|
|
|
#include <drm/drm_encoder.h>
|
|
|
|
#include <drm/drm_of.h>
|
|
|
|
#include <drm/drm_panel.h>
|
|
|
|
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/component.h>
|
|
|
|
#include <linux/iopoll.h>
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
#include <linux/of_device.h>
|
2017-05-28 00:09:35 +08:00
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/pm_runtime.h>
|
2017-10-10 11:20:01 +08:00
|
|
|
#include <linux/regmap.h>
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
#include <linux/reset.h>
|
2017-05-28 00:09:35 +08:00
|
|
|
|
|
|
|
#include "sun4i_backend.h"
|
|
|
|
#include "sun4i_crtc.h"
|
|
|
|
#include "sun4i_drv.h"
|
|
|
|
#include "sun4i_hdmi.h"
|
|
|
|
|
|
|
|
static inline struct sun4i_hdmi *
|
|
|
|
drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
return container_of(encoder, struct sun4i_hdmi,
|
|
|
|
encoder);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct sun4i_hdmi *
|
|
|
|
drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
|
|
|
|
{
|
|
|
|
return container_of(connector, struct sun4i_hdmi,
|
|
|
|
connector);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
|
|
|
|
struct drm_display_mode *mode)
|
|
|
|
{
|
|
|
|
struct hdmi_avi_infoframe frame;
|
|
|
|
u8 buffer[17];
|
|
|
|
int i, ret;
|
|
|
|
|
drm: handle HDMI 2.0 VICs in AVI info-frames
HDMI 1.4b support the CEA video modes as per range of CEA-861-D (VIC 1-64).
For any other mode, the VIC filed in AVI infoframes should be 0.
HDMI 2.0 sinks, support video modes range as per CEA-861-F spec, which is
extended to (VIC 1-107).
This patch adds a bool input variable, which indicates if the connected
sink is a HDMI 2.0 sink or not. This will make sure that we don't pass a
HDMI 2.0 VIC to a HDMI 1.4 sink.
This patch touches all drm drivers, who are callers of this function
drm_hdmi_avi_infoframe_from_display_mode but to make sure there is
no change in current behavior, is_hdmi2 is kept as false.
In case of I915 driver, this patch:
- checks if the connected display is HDMI 2.0.
- HDMI infoframes carry one of this two type of information:
- VIC for 4K modes for HDMI 1.4 sinks
- S3D information for S3D modes
As CEA-861-F has already defined VICs for 4K videomodes, this
patch doesn't allow sending HDMI infoframes for HDMI 2.0 sinks,
until the mode is 3D.
Cc: Ville Syrjala <ville.syrjala@linux.intel.com>
Cc: Jose Abreu <jose.abreu@synopsys.com>
Cc: Andrzej Hajda <a.hajda@samsung.com>
Cc: Alex Deucher <alexander.deucher@amd.com>
Cc: Daniel Vetter <daniel.vetter@intel.com>
PS: This patch touches a few lines in few files, which were
already above 80 char, so checkpatch gives 80 char warning again.
- gpu/drm/omapdrm/omap_encoder.c
- gpu/drm/i915/intel_sdvo.c
V2: Rebase, Added r-b from Andrzej
V3: Addressed review comment from Ville:
- Do not send VICs in both AVI-IF and HDMI-IF
send only one of it.
V4: Rebase
V5: Added r-b from Neil.
Addressed review comments from Ville
- Do not block HDMI vendor IF, instead check for VIC while
handling AVI infoframes
V6: Rebase
V7: Rebase
Reviewed-by: Andrzej Hajda <a.hajda@samsung.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
Signed-off-by: Shashank Sharma <shashank.sharma@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/1499960000-9232-2-git-send-email-shashank.sharma@intel.com
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
2017-07-13 23:33:07 +08:00
|
|
|
ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, false);
|
2017-05-28 00:09:35 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
DRM_ERROR("Failed to get infoframes from mode\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
|
|
|
|
if (ret < 0) {
|
|
|
|
DRM_ERROR("Failed to pack infoframes\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < sizeof(buffer); i++)
|
|
|
|
writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
|
|
|
|
struct drm_crtc_state *crtc_state,
|
|
|
|
struct drm_connector_state *conn_state)
|
|
|
|
{
|
|
|
|
struct drm_display_mode *mode = &crtc_state->mode;
|
|
|
|
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sun4i_hdmi_disable(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
|
|
|
|
|
|
|
|
val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
|
|
|
|
val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
|
|
|
|
writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sun4i_hdmi_enable(struct drm_encoder *encoder)
|
|
|
|
{
|
|
|
|
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
|
|
|
|
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
|
|
|
|
u32 val = 0;
|
|
|
|
|
|
|
|
DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
|
|
|
|
|
|
|
|
sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
|
|
|
|
val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
|
|
|
|
val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
|
|
|
|
writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
|
|
|
|
|
|
|
|
val = SUN4I_HDMI_VID_CTRL_ENABLE;
|
|
|
|
if (hdmi->hdmi_monitor)
|
|
|
|
val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
|
|
|
|
|
|
|
|
writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
|
|
|
|
struct drm_display_mode *mode,
|
|
|
|
struct drm_display_mode *adjusted_mode)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
|
|
|
|
unsigned int x, y;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
|
|
|
|
clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
|
|
|
|
|
|
|
|
/* Set input sync enable */
|
|
|
|
writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
|
|
|
|
hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
|
|
|
|
|
2017-10-14 12:02:52 +08:00
|
|
|
/*
|
|
|
|
* Setup output pad (?) controls
|
|
|
|
*
|
|
|
|
* This is done here instead of at probe/bind time because
|
|
|
|
* the controller seems to toggle some of the bits on its own.
|
|
|
|
*
|
|
|
|
* We can't just initialize the register there, we need to
|
|
|
|
* protect the clock bits that have already been read out and
|
|
|
|
* cached by the clock framework.
|
|
|
|
*/
|
|
|
|
val = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
|
|
|
|
val &= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
|
|
|
|
val |= hdmi->variant->pad_ctrl1_init_val;
|
|
|
|
writel(val, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
|
|
|
|
val = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
|
|
|
|
|
2017-05-28 00:09:35 +08:00
|
|
|
/* Setup timing registers */
|
|
|
|
writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
|
|
|
|
SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
|
|
|
|
hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
|
|
|
|
|
|
|
|
x = mode->htotal - mode->hsync_start;
|
|
|
|
y = mode->vtotal - mode->vsync_start;
|
|
|
|
writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
|
|
|
|
hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
|
|
|
|
|
|
|
|
x = mode->hsync_start - mode->hdisplay;
|
|
|
|
y = mode->vsync_start - mode->vdisplay;
|
|
|
|
writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
|
|
|
|
hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
|
|
|
|
|
|
|
|
x = mode->hsync_end - mode->hsync_start;
|
|
|
|
y = mode->vsync_end - mode->vsync_start;
|
|
|
|
writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
|
|
|
|
hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
|
|
|
|
|
|
|
|
val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
|
|
|
val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
|
|
|
|
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
|
|
|
val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
|
|
|
|
|
|
|
|
writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
|
|
|
|
.atomic_check = sun4i_hdmi_atomic_check,
|
|
|
|
.disable = sun4i_hdmi_disable,
|
|
|
|
.enable = sun4i_hdmi_enable,
|
|
|
|
.mode_set = sun4i_hdmi_mode_set,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
|
|
|
|
.destroy = drm_encoder_cleanup,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int sun4i_hdmi_get_modes(struct drm_connector *connector)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
|
|
|
|
struct edid *edid;
|
|
|
|
int ret;
|
|
|
|
|
2017-07-02 15:27:10 +08:00
|
|
|
edid = drm_get_edid(connector, hdmi->i2c);
|
2017-05-28 00:09:35 +08:00
|
|
|
if (!edid)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
|
|
|
|
DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
|
|
|
|
hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
|
|
|
|
|
|
|
|
drm_mode_connector_update_edid_property(connector, edid);
|
2017-07-11 14:30:44 +08:00
|
|
|
cec_s_phys_addr_from_edid(hdmi->cec_adap, edid);
|
2017-05-28 00:09:35 +08:00
|
|
|
ret = drm_add_edid_modes(connector, edid);
|
|
|
|
kfree(edid);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
|
|
|
|
.get_modes = sun4i_hdmi_get_modes,
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum drm_connector_status
|
|
|
|
sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
|
|
|
|
unsigned long reg;
|
|
|
|
|
|
|
|
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
|
|
|
|
reg & SUN4I_HDMI_HPD_HIGH,
|
2017-07-11 14:30:44 +08:00
|
|
|
0, 500000)) {
|
|
|
|
cec_phys_addr_invalidate(hdmi->cec_adap);
|
2017-05-28 00:09:35 +08:00
|
|
|
return connector_status_disconnected;
|
2017-07-11 14:30:44 +08:00
|
|
|
}
|
2017-05-28 00:09:35 +08:00
|
|
|
|
|
|
|
return connector_status_connected;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
|
|
|
|
.detect = sun4i_hdmi_connector_detect,
|
|
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
|
|
.destroy = drm_connector_cleanup,
|
|
|
|
.reset = drm_atomic_helper_connector_reset,
|
|
|
|
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
|
|
|
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
|
|
|
};
|
|
|
|
|
2017-07-11 14:30:44 +08:00
|
|
|
#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
|
|
|
|
static bool sun4i_hdmi_cec_pin_read(struct cec_adapter *adap)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
|
|
|
|
|
|
|
|
return readl(hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
|
|
|
|
|
|
|
|
/* Start driving the CEC pin low */
|
|
|
|
writel(SUN4I_HDMI_CEC_ENABLE, hdmi->base + SUN4I_HDMI_CEC);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Stop driving the CEC pin, the pull up will take over
|
|
|
|
* unless another CEC device is driving the pin low.
|
|
|
|
*/
|
|
|
|
writel(0, hdmi->base + SUN4I_HDMI_CEC);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
|
|
|
|
.read = sun4i_hdmi_cec_pin_read,
|
|
|
|
.low = sun4i_hdmi_cec_pin_low,
|
|
|
|
.high = sun4i_hdmi_cec_pin_high,
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
#define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0))
|
|
|
|
#define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0))
|
|
|
|
|
2017-10-17 20:18:00 +08:00
|
|
|
/* Only difference from sun5i is AMP is 4 instead of 6 */
|
|
|
|
static const struct sun4i_hdmi_variant sun4i_variant = {
|
|
|
|
.pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_CKEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWENG |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWEND |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWENC |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_LDODEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_LDOCEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_BIASEN,
|
|
|
|
.pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(4) |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_AMP_OPT,
|
|
|
|
.pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_CS(7) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_CP_S(15) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_S(7) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_SDIV2 |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_LDO2_EN |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_LDO1_EN |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_HV_IS_33 |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_BWS |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_PLL_EN,
|
|
|
|
|
|
|
|
.ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6),
|
|
|
|
.ddc_clk_pre_divider = 2,
|
|
|
|
.ddc_clk_m_offset = 1,
|
|
|
|
|
|
|
|
.field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31),
|
|
|
|
.field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30),
|
|
|
|
.field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0),
|
|
|
|
.field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31),
|
|
|
|
.field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6),
|
|
|
|
.field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8),
|
|
|
|
.field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31),
|
|
|
|
.field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
|
|
|
|
.field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
|
|
|
|
.field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9),
|
|
|
|
.field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2),
|
|
|
|
.field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9),
|
|
|
|
.field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8),
|
|
|
|
|
|
|
|
.ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG,
|
|
|
|
.ddc_fifo_has_dir = true,
|
|
|
|
};
|
|
|
|
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
static const struct sun4i_hdmi_variant sun5i_variant = {
|
|
|
|
.pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_CKEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWENG |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWEND |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWENC |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_LDODEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_LDOCEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_BIASEN,
|
|
|
|
.pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_AMP_OPT,
|
|
|
|
.pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_CS(7) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_CP_S(15) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_S(7) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_SDIV2 |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_LDO2_EN |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_LDO1_EN |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_HV_IS_33 |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_BWS |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_PLL_EN,
|
|
|
|
|
|
|
|
.ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6),
|
|
|
|
.ddc_clk_pre_divider = 2,
|
|
|
|
.ddc_clk_m_offset = 1,
|
|
|
|
|
|
|
|
.field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31),
|
|
|
|
.field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30),
|
|
|
|
.field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0),
|
|
|
|
.field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31),
|
|
|
|
.field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6),
|
|
|
|
.field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8),
|
|
|
|
.field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31),
|
|
|
|
.field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
|
|
|
|
.field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
|
|
|
|
.field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9),
|
|
|
|
.field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2),
|
|
|
|
.field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9),
|
|
|
|
.field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8),
|
|
|
|
|
|
|
|
.ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG,
|
|
|
|
.ddc_fifo_has_dir = true,
|
|
|
|
};
|
|
|
|
|
2017-10-10 11:20:06 +08:00
|
|
|
static const struct sun4i_hdmi_variant sun6i_variant = {
|
|
|
|
.has_ddc_parent_clk = true,
|
|
|
|
.has_reset_control = true,
|
|
|
|
.pad_ctrl0_init_val = 0xff |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_TXEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_CKEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWENG |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWEND |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_PWENC |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_LDODEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL0_LDOCEN,
|
|
|
|
.pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_EMP(4) |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_PWSDT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_PWSCK |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_AMP_OPT |
|
|
|
|
SUN4I_HDMI_PAD_CTRL1_UNKNOWN,
|
|
|
|
.pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_CS(3) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_CP_S(10) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_S(4) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_SDIV2 |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_LDO2_EN |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_LDO1_EN |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_HV_IS_33 |
|
|
|
|
SUN4I_HDMI_PLL_CTRL_PLL_EN,
|
|
|
|
|
|
|
|
.ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6),
|
|
|
|
.ddc_clk_pre_divider = 1,
|
|
|
|
.ddc_clk_m_offset = 2,
|
|
|
|
|
|
|
|
.tmds_clk_div_offset = 1,
|
|
|
|
|
|
|
|
.field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0),
|
|
|
|
.field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27),
|
|
|
|
.field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31),
|
|
|
|
.field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31),
|
|
|
|
.field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7),
|
|
|
|
.field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8),
|
|
|
|
.field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18),
|
|
|
|
.field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
|
|
|
|
.field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
|
|
|
|
.field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25),
|
|
|
|
.field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2),
|
|
|
|
.field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6),
|
|
|
|
.field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4),
|
|
|
|
|
|
|
|
.ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG,
|
|
|
|
.ddc_fifo_thres_incl = true,
|
|
|
|
};
|
|
|
|
|
2017-10-10 11:20:01 +08:00
|
|
|
static const struct regmap_config sun4i_hdmi_regmap_config = {
|
|
|
|
.reg_bits = 32,
|
|
|
|
.val_bits = 32,
|
|
|
|
.reg_stride = 4,
|
|
|
|
.max_register = 0x580,
|
|
|
|
};
|
|
|
|
|
2017-05-28 00:09:35 +08:00
|
|
|
static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
struct drm_device *drm = data;
|
|
|
|
struct sun4i_drv *drv = drm->dev_private;
|
|
|
|
struct sun4i_hdmi *hdmi;
|
|
|
|
struct resource *res;
|
|
|
|
u32 reg;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
|
|
|
|
if (!hdmi)
|
|
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(dev, hdmi);
|
|
|
|
hdmi->dev = dev;
|
|
|
|
hdmi->drv = drv;
|
|
|
|
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
hdmi->variant = of_device_get_match_data(dev);
|
|
|
|
if (!hdmi->variant)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2017-05-28 00:09:35 +08:00
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
hdmi->base = devm_ioremap_resource(dev, res);
|
|
|
|
if (IS_ERR(hdmi->base)) {
|
|
|
|
dev_err(dev, "Couldn't map the HDMI encoder registers\n");
|
|
|
|
return PTR_ERR(hdmi->base);
|
|
|
|
}
|
|
|
|
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
if (hdmi->variant->has_reset_control) {
|
|
|
|
hdmi->reset = devm_reset_control_get(dev, NULL);
|
|
|
|
if (IS_ERR(hdmi->reset)) {
|
|
|
|
dev_err(dev, "Couldn't get the HDMI reset control\n");
|
|
|
|
return PTR_ERR(hdmi->reset);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = reset_control_deassert(hdmi->reset);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "Couldn't deassert HDMI reset\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-28 00:09:35 +08:00
|
|
|
hdmi->bus_clk = devm_clk_get(dev, "ahb");
|
|
|
|
if (IS_ERR(hdmi->bus_clk)) {
|
|
|
|
dev_err(dev, "Couldn't get the HDMI bus clock\n");
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
ret = PTR_ERR(hdmi->bus_clk);
|
|
|
|
goto err_assert_reset;
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
clk_prepare_enable(hdmi->bus_clk);
|
|
|
|
|
|
|
|
hdmi->mod_clk = devm_clk_get(dev, "mod");
|
|
|
|
if (IS_ERR(hdmi->mod_clk)) {
|
|
|
|
dev_err(dev, "Couldn't get the HDMI mod clock\n");
|
2017-10-10 11:20:00 +08:00
|
|
|
ret = PTR_ERR(hdmi->mod_clk);
|
|
|
|
goto err_disable_bus_clk;
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
clk_prepare_enable(hdmi->mod_clk);
|
|
|
|
|
|
|
|
hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
|
|
|
|
if (IS_ERR(hdmi->pll0_clk)) {
|
|
|
|
dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
|
2017-10-10 11:20:00 +08:00
|
|
|
ret = PTR_ERR(hdmi->pll0_clk);
|
|
|
|
goto err_disable_mod_clk;
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
|
|
|
|
if (IS_ERR(hdmi->pll1_clk)) {
|
|
|
|
dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
|
2017-10-10 11:20:00 +08:00
|
|
|
ret = PTR_ERR(hdmi->pll1_clk);
|
|
|
|
goto err_disable_mod_clk;
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
|
2017-10-10 11:20:01 +08:00
|
|
|
hdmi->regmap = devm_regmap_init_mmio(dev, hdmi->base,
|
|
|
|
&sun4i_hdmi_regmap_config);
|
|
|
|
if (IS_ERR(hdmi->regmap)) {
|
|
|
|
dev_err(dev, "Couldn't create HDMI encoder regmap\n");
|
|
|
|
return PTR_ERR(hdmi->regmap);
|
|
|
|
}
|
|
|
|
|
2017-05-28 00:09:35 +08:00
|
|
|
ret = sun4i_tmds_create(hdmi);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "Couldn't create the TMDS clock\n");
|
2017-10-10 11:20:00 +08:00
|
|
|
goto err_disable_mod_clk;
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
if (hdmi->variant->has_ddc_parent_clk) {
|
|
|
|
hdmi->ddc_parent_clk = devm_clk_get(dev, "ddc");
|
|
|
|
if (IS_ERR(hdmi->ddc_parent_clk)) {
|
|
|
|
dev_err(dev, "Couldn't get the HDMI DDC clock\n");
|
|
|
|
return PTR_ERR(hdmi->ddc_parent_clk);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hdmi->ddc_parent_clk = hdmi->tmds_clk;
|
|
|
|
}
|
|
|
|
|
2017-05-28 00:09:35 +08:00
|
|
|
writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
|
|
|
|
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
writel(hdmi->variant->pad_ctrl0_init_val,
|
2017-05-28 00:09:35 +08:00
|
|
|
hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
|
|
|
|
|
|
|
|
reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
|
|
|
|
reg &= SUN4I_HDMI_PLL_CTRL_DIV_MASK;
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
reg |= hdmi->variant->pll_ctrl_init_val;
|
2017-05-28 00:09:35 +08:00
|
|
|
writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
|
|
|
|
|
2017-07-02 15:27:10 +08:00
|
|
|
ret = sun4i_hdmi_i2c_create(dev, hdmi);
|
2017-05-28 00:09:35 +08:00
|
|
|
if (ret) {
|
2017-07-02 15:27:10 +08:00
|
|
|
dev_err(dev, "Couldn't create the HDMI I2C adapter\n");
|
2017-10-10 11:20:00 +08:00
|
|
|
goto err_disable_mod_clk;
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
drm_encoder_helper_add(&hdmi->encoder,
|
|
|
|
&sun4i_hdmi_helper_funcs);
|
|
|
|
ret = drm_encoder_init(drm,
|
|
|
|
&hdmi->encoder,
|
|
|
|
&sun4i_hdmi_funcs,
|
|
|
|
DRM_MODE_ENCODER_TMDS,
|
|
|
|
NULL);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "Couldn't initialise the HDMI encoder\n");
|
2017-07-02 15:27:10 +08:00
|
|
|
goto err_del_i2c_adapter;
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
|
|
|
|
dev->of_node);
|
2017-07-02 15:27:10 +08:00
|
|
|
if (!hdmi->encoder.possible_crtcs) {
|
|
|
|
ret = -EPROBE_DEFER;
|
|
|
|
goto err_del_i2c_adapter;
|
|
|
|
}
|
2017-05-28 00:09:35 +08:00
|
|
|
|
2017-07-11 14:30:44 +08:00
|
|
|
#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
|
|
|
|
hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops,
|
|
|
|
hdmi, "sun4i", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS |
|
|
|
|
CEC_CAP_PASSTHROUGH | CEC_CAP_RC);
|
|
|
|
ret = PTR_ERR_OR_ZERO(hdmi->cec_adap);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_cleanup_connector;
|
|
|
|
writel(readl(hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX,
|
|
|
|
hdmi->base + SUN4I_HDMI_CEC);
|
|
|
|
#endif
|
2017-05-28 00:09:35 +08:00
|
|
|
|
|
|
|
drm_connector_helper_add(&hdmi->connector,
|
|
|
|
&sun4i_hdmi_connector_helper_funcs);
|
|
|
|
ret = drm_connector_init(drm, &hdmi->connector,
|
|
|
|
&sun4i_hdmi_connector_funcs,
|
|
|
|
DRM_MODE_CONNECTOR_HDMIA);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev,
|
|
|
|
"Couldn't initialise the HDMI connector\n");
|
|
|
|
goto err_cleanup_connector;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* There is no HPD interrupt, so we need to poll the controller */
|
|
|
|
hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
|
|
|
|
DRM_CONNECTOR_POLL_DISCONNECT;
|
|
|
|
|
2017-07-11 14:30:44 +08:00
|
|
|
ret = cec_register_adapter(hdmi->cec_adap, dev);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_cleanup_connector;
|
2017-05-28 00:09:35 +08:00
|
|
|
drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_cleanup_connector:
|
2017-07-11 14:30:44 +08:00
|
|
|
cec_delete_adapter(hdmi->cec_adap);
|
2017-05-28 00:09:35 +08:00
|
|
|
drm_encoder_cleanup(&hdmi->encoder);
|
2017-07-02 15:27:10 +08:00
|
|
|
err_del_i2c_adapter:
|
|
|
|
i2c_del_adapter(hdmi->i2c);
|
2017-10-10 11:20:00 +08:00
|
|
|
err_disable_mod_clk:
|
|
|
|
clk_disable_unprepare(hdmi->mod_clk);
|
|
|
|
err_disable_bus_clk:
|
|
|
|
clk_disable_unprepare(hdmi->bus_clk);
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
err_assert_reset:
|
|
|
|
reset_control_assert(hdmi->reset);
|
2017-05-28 00:09:35 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
|
|
|
|
|
2017-07-11 14:30:44 +08:00
|
|
|
cec_unregister_adapter(hdmi->cec_adap);
|
2017-05-28 00:09:35 +08:00
|
|
|
drm_connector_cleanup(&hdmi->connector);
|
|
|
|
drm_encoder_cleanup(&hdmi->encoder);
|
2017-07-02 15:27:10 +08:00
|
|
|
i2c_del_adapter(hdmi->i2c);
|
2017-10-10 11:20:00 +08:00
|
|
|
clk_disable_unprepare(hdmi->mod_clk);
|
|
|
|
clk_disable_unprepare(hdmi->bus_clk);
|
2017-05-28 00:09:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct component_ops sun4i_hdmi_ops = {
|
|
|
|
.bind = sun4i_hdmi_bind,
|
|
|
|
.unbind = sun4i_hdmi_unbind,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int sun4i_hdmi_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
return component_add(&pdev->dev, &sun4i_hdmi_ops);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sun4i_hdmi_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
component_del(&pdev->dev, &sun4i_hdmi_ops);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct of_device_id sun4i_hdmi_of_table[] = {
|
2017-10-17 20:18:00 +08:00
|
|
|
{ .compatible = "allwinner,sun4i-a10-hdmi", .data = &sun4i_variant, },
|
drm/sun4i: hdmi: Add support for controller hardware variants
The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:
- Need different initial values for the PLL related registers
- Different behavior of the DDC and TMDS clocks
- Different register layout for the DDC portion
- Separate DDC parent clock on the A31
- Explicit reset control
For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.
A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.
Regmap fields are used to deal with the different register layout
of the DDC block.
Signed-off-by: Chen-Yu Tsai <wens@csie.org>
Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20171010032008.682-8-wens@csie.org
2017-10-10 11:20:04 +08:00
|
|
|
{ .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, },
|
2017-10-10 11:20:06 +08:00
|
|
|
{ .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_variant, },
|
2017-05-28 00:09:35 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
|
|
|
|
|
|
|
|
static struct platform_driver sun4i_hdmi_driver = {
|
|
|
|
.probe = sun4i_hdmi_probe,
|
|
|
|
.remove = sun4i_hdmi_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = "sun4i-hdmi",
|
|
|
|
.of_match_table = sun4i_hdmi_of_table,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
module_platform_driver(sun4i_hdmi_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
|
|
|
|
MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
|
|
|
|
MODULE_LICENSE("GPL");
|