0
0
mirror of https://github.com/libretro/Lakka-LibreELEC.git synced 2025-03-01 11:31:22 +00:00
Lakka-LibreELEC/projects/Allwinner/devices/H3/patches/linux/0001-HACK-SW-CEC-implementation-for-H3.patch
2021-10-27 23:37:14 +02:00

228 lines
7.5 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@gmail.com>
Date: Mon, 11 Oct 2021 20:13:41 +0200
Subject: [PATCH] HACK: SW CEC implementation for H3
Many H3 boards lack 32768 Hz external oscillator, so internal RC is used
instead. However, it's too unstable for CEC. Use SW implementation
instead. That makes it usable, albeit sensitive to cpufreq changes.
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 +-
drivers/gpu/drm/sun4i/Kconfig | 2 +
drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h | 12 ++++
drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 76 ++++++++++++++++++++++-
include/drm/bridge/dw_hdmi.h | 2 +
5 files changed, 91 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 0c79a9ba48bb..d062815b6c92 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3421,7 +3421,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
hdmi->audio = platform_device_register_full(&pdevinfo);
}
- if (config0 & HDMI_CONFIG0_CEC) {
+ if (!plat_data->is_cec_unusable && (config0 & HDMI_CONFIG0_CEC)) {
cec.hdmi = hdmi;
cec.ops = &dw_hdmi_cec_ops;
cec.irq = irq;
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index 5755f0432e77..b93eb2fb52ce 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -56,6 +56,8 @@ config DRM_SUN8I_DW_HDMI
tristate "Support for Allwinner version of DesignWare HDMI"
depends on DRM_SUN4I
select DRM_DW_HDMI
+ select CEC_CORE
+ select CEC_PIN
help
Choose this option if you have an Allwinner SoC with the
DesignWare HDMI controller with custom HDMI PHY. If M is
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
index bffe1b9cd3dc..438f7398e26d 100644
--- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
@@ -13,6 +13,8 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
+#include <media/cec-notifier.h>
+#include <media/cec-pin.h>
#define SUN8I_HDMI_PHY_DBG_CTRL_REG 0x0000
#define SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK BIT(0)
@@ -145,6 +147,13 @@
#define SUN8I_HDMI_PHY_ANA_STS_RCAL_MASK GENMASK(5, 0)
#define SUN8I_HDMI_PHY_CEC_REG 0x003c
+#define SUN8I_HDMI_PHY_CEC_PIN_CTRL BIT(7)
+/*
+ * Documentation says that this bit is output enable. However,
+ * it seems that this bit is actually output disable.
+ */
+#define SUN8I_HDMI_PHY_CEC_OUT_DIS BIT(2)
+#define SUN8I_HDMI_PHY_CEC_IN_DATA BIT(1)
struct sun8i_hdmi_phy;
@@ -152,6 +161,7 @@ struct sun8i_hdmi_phy_variant {
bool has_phy_clk;
bool has_second_pll;
unsigned int is_custom_phy : 1;
+ unsigned int bit_bang_cec : 1;
const struct dw_hdmi_curr_ctrl *cur_ctr;
const struct dw_hdmi_mpll_config *mpll_cfg;
const struct dw_hdmi_phy_config *phy_cfg;
@@ -164,6 +174,8 @@ struct sun8i_hdmi_phy_variant {
};
struct sun8i_hdmi_phy {
+ struct cec_adapter *cec_adapter;
+ struct cec_notifier *cec_notifier;
struct clk *clk_bus;
struct clk *clk_mod;
struct clk *clk_phy;
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
index b64d93da651d..4e324e7f4b64 100644
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
@@ -498,8 +498,9 @@ static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy)
regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK, 0);
- /* set HW control of CEC pins */
- regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG, 0);
+ /* manual control of CEC pins */
+ regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG,
+ phy->variant->bit_bang_cec ? SUN8I_HDMI_PHY_CEC_PIN_CTRL : 0);
/* read calibration data */
regmap_read(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, &val);
@@ -576,8 +577,47 @@ void sun8i_hdmi_phy_set_ops(struct sun8i_hdmi_phy *phy,
plat_data->cur_ctr = variant->cur_ctr;
plat_data->phy_config = variant->phy_cfg;
}
+ plat_data->is_cec_unusable = phy->variant->bit_bang_cec;
}
+static int sun8i_hdmi_phy_cec_pin_read(struct cec_adapter *adap)
+{
+ struct sun8i_hdmi_phy *phy = cec_get_drvdata(adap);
+ unsigned int val;
+
+ regmap_read(phy->regs, SUN8I_HDMI_PHY_CEC_REG, &val);
+
+ return val & SUN8I_HDMI_PHY_CEC_IN_DATA;
+}
+
+static void sun8i_hdmi_phy_cec_pin_low(struct cec_adapter *adap)
+{
+ struct sun8i_hdmi_phy *phy = cec_get_drvdata(adap);
+
+ /* Start driving the CEC pin low */
+ regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG,
+ SUN8I_HDMI_PHY_CEC_PIN_CTRL);
+}
+
+static void sun8i_hdmi_phy_cec_pin_high(struct cec_adapter *adap)
+{
+ struct sun8i_hdmi_phy *phy = cec_get_drvdata(adap);
+
+ /*
+ * Stop driving the CEC pin, the pull up will take over
+ * unless another CEC device is driving the pin low.
+ */
+ regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG,
+ SUN8I_HDMI_PHY_CEC_PIN_CTRL |
+ SUN8I_HDMI_PHY_CEC_OUT_DIS);
+}
+
+static const struct cec_pin_ops sun8i_hdmi_phy_cec_pin_ops = {
+ .read = sun8i_hdmi_phy_cec_pin_read,
+ .low = sun8i_hdmi_phy_cec_pin_low,
+ .high = sun8i_hdmi_phy_cec_pin_high,
+};
+
static const struct regmap_config sun8i_hdmi_phy_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
@@ -594,6 +634,7 @@ static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
};
static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
+ .bit_bang_cec = true,
.has_phy_clk = true,
.is_custom_phy = true,
.phy_init = &sun8i_hdmi_phy_init_h3,
@@ -653,6 +694,7 @@ int sun8i_hdmi_phy_get(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
{
struct platform_device *pdev = of_find_device_by_node(node);
struct sun8i_hdmi_phy *phy;
+ int ret;
if (!pdev)
return -EPROBE_DEFER;
@@ -664,8 +706,35 @@ int sun8i_hdmi_phy_get(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
hdmi->phy = phy;
put_device(&pdev->dev);
+
+ if (phy->variant->bit_bang_cec) {
+ phy->cec_adapter =
+ cec_pin_allocate_adapter(&sun8i_hdmi_phy_cec_pin_ops,
+ phy, "sun8i-cec",
+ CEC_CAP_DEFAULTS);
+ ret = PTR_ERR_OR_ZERO(phy->cec_adapter);
+ if (ret < 0)
+ return 0;
+
+ phy->cec_notifier = cec_notifier_cec_adap_register(hdmi->dev, NULL, phy->cec_adapter);
+ if (!phy->cec_notifier) {
+ ret = -ENOMEM;
+ goto err_delete_cec_adapter;
+ }
+
+ ret = cec_register_adapter(phy->cec_adapter, hdmi->dev);
+ if (ret < 0)
+ goto err_put_cec_notifier;
+ }
return 0;
+
+err_put_cec_notifier:
+ cec_notifier_cec_adap_unregister(phy->cec_notifier, phy->cec_adapter);
+err_delete_cec_adapter:
+ cec_delete_adapter(phy->cec_adapter);
+
+ return ret;
}
static int sun8i_hdmi_phy_probe(struct platform_device *pdev)
@@ -768,6 +837,9 @@ static int sun8i_hdmi_phy_remove(struct platform_device *pdev)
{
struct sun8i_hdmi_phy *phy = platform_get_drvdata(pdev);
+ cec_notifier_cec_adap_unregister(phy->cec_notifier, phy->cec_adapter);
+ cec_unregister_adapter(phy->cec_adapter);
+
reset_control_put(phy->rst_phy);
clk_put(phy->clk_pll0);
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index ea34ca146b82..e66a4edc2a93 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -153,6 +153,8 @@ struct dw_hdmi_plat_data {
const struct dw_hdmi_phy_config *phy_config;
int (*configure_phy)(struct dw_hdmi *hdmi, void *data,
unsigned long mpixelclock);
+
+ unsigned int is_cec_unusable : 1;
};
struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,