Files
build/patch/kernel/archive/meson-6.12/0014-phy-amlogic-Add-a-new-driver-for-the-CVBS-DAC-CVBS-P.patch
Dominik Wójt 5b29f4dd4b meson: kernel update: legacy -> 6.6, current -> 6.12 (#7801)
* Add MXQ target. Copy HDMI fix from odroid-c1.
* meson8, MXQ: add boot from usb support, configurable dtb
* MXQ: remove boot logo
Built-in U-BOOT is used, so the logo will not be displayed anyway.
* meson: kernel update: legacy -> 6.6, current -> 6.12
* Change Odroid C1 and Onecloud to community supported as build now passes
---------
Co-authored-by: Igor Pecovnik <igor@armbian.com>
2025-02-08 23:08:34 +01:00

446 lines
14 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Date: Mon, 11 Oct 2021 23:05:25 +0200
Subject: phy: amlogic: Add a new driver for the CVBS DAC (CVBS PHY)
Amlogic Meson SoCs embed a CVBS DAC which converts the signal from the
VPU to analog. The IP has evolved over time with the SoC generations:
- Meson8/8b/8m2 has per-chip calibrated data for the CDAC_GSW register
- GXBB is overall similar to Meson8/8b/8m2 except that it doesn't have
per-chip calibration data and uses 0x0 in CDAC_GSW always
- GXL/GXM are overall similar to GXBB but require a CDAC_VREF_ADJ value
of 0xf (of which the actual meaning is unknown)
- G12A/G12B/SM1 use different register offsets and different values for
CDAC_CTRL_RESV2, CDAC_VREF_ADJ and CDAC_RL_ADJ. Like other SoCs from
GXBB onwards they don't need any per-chip data.
For backwards compatibility with old .dtbs the driver gets
platform_device_id's so the VPU driver can register the PHY as platform
device if not provided via .dtb.
Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
---
drivers/phy/amlogic/Kconfig | 10 +
drivers/phy/amlogic/Makefile | 1 +
drivers/phy/amlogic/phy-meson-cvbs-dac.c | 376 ++++++++++
3 files changed, 387 insertions(+)
diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/phy/amlogic/Kconfig
+++ b/drivers/phy/amlogic/Kconfig
@@ -25,6 +25,16 @@ config PHY_MESON8B_USB2
Meson8b and GXBB SoCs.
If unsure, say N.
+config PHY_MESON_CVBS_DAC
+ tristate "Amlogic Meson CVBS DAC PHY driver"
+ depends on ARCH_MESON || COMPILE_TEST
+ depends on OF
+ select MFD_SYSCON
+ help
+ Enable this to support the CVBS DAC (PHY) found in Amlogic
+ Meson SoCs.
+ If unsure, say N.
+
config PHY_MESON_GXL_USB2
tristate "Meson GXL and GXM USB2 PHY drivers"
default ARCH_MESON
diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile
index 111111111111..222222222222 100644
--- a/drivers/phy/amlogic/Makefile
+++ b/drivers/phy/amlogic/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_PHY_MESON8_HDMI_TX) += phy-meson8-hdmi-tx.o
obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o
+obj-$(CONFIG_PHY_MESON_CVBS_DAC) += phy-meson-cvbs-dac.o
obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o
obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o
obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o
diff --git a/drivers/phy/amlogic/phy-meson-cvbs-dac.c b/drivers/phy/amlogic/phy-meson-cvbs-dac.c
new file mode 100644
index 000000000000..111111111111
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-cvbs-dac.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 BayLibre, SAS
+ * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/property.h>
+#include <linux/mfd/syscon.h>
+#include <linux/nvmem-consumer.h>
+
+#define HHI_VDAC_CNTL0_MESON8 0x2F4 /* 0xbd offset in data sheet */
+#define HHI_VDAC_CNTL1_MESON8 0x2F8 /* 0xbe offset in data sheet */
+
+#define HHI_VDAC_CNTL0_G12A 0x2EC /* 0xbd offset in data sheet */
+#define HHI_VDAC_CNTL1_G12A 0x2F0 /* 0xbe offset in data sheet */
+
+enum phy_meson_cvbs_dac_reg {
+ MESON_CDAC_CTRL_RESV1,
+ MESON_CDAC_CTRL_RESV2,
+ MESON_CDAC_VREF_ADJ,
+ MESON_CDAC_RL_ADJ,
+ MESON_CDAC_CLK_PHASE_SEL,
+ MESON_CDAC_DRIVER_ADJ,
+ MESON_CDAC_EXT_VREF_EN,
+ MESON_CDAC_BIAS_C,
+ MESON_VDAC_CNTL0_RESERVED,
+ MESON_CDAC_GSW,
+ MESON_CDAC_PWD,
+ MESON_VDAC_CNTL1_RESERVED,
+ MESON_CVBS_DAC_NUM_REGS
+};
+
+struct phy_meson_cvbs_dac_data {
+ const struct reg_field *reg_fields;
+ u8 cdac_ctrl_resv2_enable_val;
+ u8 cdac_vref_adj_enable_val;
+ u8 cdac_rl_adj_enable_val;
+ u8 cdac_pwd_disable_val;
+ bool needs_cvbs_trimming_nvmem_cell;
+};
+
+struct phy_meson_cvbs_dac_priv {
+ struct regmap_field *regs[MESON_CVBS_DAC_NUM_REGS];
+ const struct phy_meson_cvbs_dac_data *data;
+ u8 cdac_gsw_enable_val;
+};
+
+static const struct reg_field phy_meson8_cvbs_dac_reg_fields[] = {
+ [MESON_CDAC_CTRL_RESV1] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 0, 7),
+ [MESON_CDAC_CTRL_RESV2] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 8, 15),
+ [MESON_CDAC_VREF_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 16, 20),
+ [MESON_CDAC_RL_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 21, 23),
+ [MESON_CDAC_CLK_PHASE_SEL] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 24, 24),
+ [MESON_CDAC_DRIVER_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 25, 25),
+ [MESON_CDAC_EXT_VREF_EN] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 26, 26),
+ [MESON_CDAC_BIAS_C] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 27, 27),
+ [MESON_VDAC_CNTL0_RESERVED] = REG_FIELD(HHI_VDAC_CNTL0_MESON8, 28, 31),
+ [MESON_CDAC_GSW] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 0, 2),
+ [MESON_CDAC_PWD] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 3, 3),
+ [MESON_VDAC_CNTL1_RESERVED] = REG_FIELD(HHI_VDAC_CNTL1_MESON8, 4, 31),
+};
+
+static const struct reg_field phy_meson_g12a_cvbs_dac_reg_fields[] = {
+ [MESON_CDAC_CTRL_RESV1] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 0, 7),
+ [MESON_CDAC_CTRL_RESV2] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 8, 15),
+ [MESON_CDAC_VREF_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 16, 20),
+ [MESON_CDAC_RL_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 21, 23),
+ [MESON_CDAC_CLK_PHASE_SEL] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 24, 24),
+ [MESON_CDAC_DRIVER_ADJ] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 25, 25),
+ [MESON_CDAC_EXT_VREF_EN] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 26, 26),
+ [MESON_CDAC_BIAS_C] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 27, 27),
+ [MESON_VDAC_CNTL0_RESERVED] = REG_FIELD(HHI_VDAC_CNTL0_G12A, 28, 31),
+ [MESON_CDAC_GSW] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 0, 2),
+ [MESON_CDAC_PWD] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 3, 3),
+ [MESON_VDAC_CNTL1_RESERVED] = REG_FIELD(HHI_VDAC_CNTL1_G12A, 4, 31),
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson8_cvbs_dac_data = {
+ .reg_fields = phy_meson8_cvbs_dac_reg_fields,
+ .cdac_ctrl_resv2_enable_val = 0x0,
+ .cdac_vref_adj_enable_val = 0x0,
+ .cdac_rl_adj_enable_val = 0x0,
+ .cdac_pwd_disable_val = 0x1,
+ .needs_cvbs_trimming_nvmem_cell = true,
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson_gxbb_cvbs_dac_data = {
+ .reg_fields = phy_meson8_cvbs_dac_reg_fields,
+ .cdac_ctrl_resv2_enable_val = 0x0,
+ .cdac_vref_adj_enable_val = 0x0,
+ .cdac_rl_adj_enable_val = 0x0,
+ .cdac_pwd_disable_val = 0x1,
+ .needs_cvbs_trimming_nvmem_cell = false,
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson_gxl_cvbs_dac_data = {
+ .reg_fields = phy_meson8_cvbs_dac_reg_fields,
+ .cdac_ctrl_resv2_enable_val = 0x0,
+ .cdac_vref_adj_enable_val = 0xf,
+ .cdac_rl_adj_enable_val = 0x0,
+ .cdac_pwd_disable_val = 0x1,
+ .needs_cvbs_trimming_nvmem_cell = false,
+};
+
+static const struct phy_meson_cvbs_dac_data phy_meson_g12a_cvbs_dac_data = {
+ .reg_fields = phy_meson_g12a_cvbs_dac_reg_fields,
+ .cdac_ctrl_resv2_enable_val = 0x60,
+ .cdac_vref_adj_enable_val = 0x10,
+ .cdac_rl_adj_enable_val = 0x4,
+ .cdac_pwd_disable_val = 0x0,
+ .needs_cvbs_trimming_nvmem_cell = false,
+};
+
+static int phy_meson_cvbs_dac_power_on(struct phy *phy)
+{
+ struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
+
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x1);
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2],
+ priv->data->cdac_ctrl_resv2_enable_val);
+ regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ],
+ priv->data->cdac_vref_adj_enable_val);
+ regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ],
+ priv->data->cdac_rl_adj_enable_val);
+ regmap_field_write(priv->regs[MESON_CDAC_GSW],
+ priv->cdac_gsw_enable_val);
+ regmap_field_write(priv->regs[MESON_CDAC_PWD], 0x0);
+
+ return 0;
+}
+
+static int phy_meson_cvbs_dac_power_off(struct phy *phy)
+{
+ struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
+
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV1], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_CTRL_RESV2], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_VREF_ADJ], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_RL_ADJ], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_GSW], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_PWD],
+ priv->data->cdac_pwd_disable_val);
+
+ return 0;
+}
+
+static int phy_meson_cvbs_dac_init(struct phy *phy)
+{
+ struct phy_meson_cvbs_dac_priv *priv = phy_get_drvdata(phy);
+
+ regmap_field_write(priv->regs[MESON_CDAC_CLK_PHASE_SEL], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_DRIVER_ADJ], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_EXT_VREF_EN], 0x0);
+ regmap_field_write(priv->regs[MESON_CDAC_BIAS_C], 0x0);
+ regmap_field_write(priv->regs[MESON_VDAC_CNTL0_RESERVED], 0x0);
+ regmap_field_write(priv->regs[MESON_VDAC_CNTL1_RESERVED], 0x0);
+
+ return phy_meson_cvbs_dac_power_off(phy);
+}
+
+static const struct phy_ops phy_meson_cvbs_dac_ops = {
+ .init = phy_meson_cvbs_dac_init,
+ .power_on = phy_meson_cvbs_dac_power_on,
+ .power_off = phy_meson_cvbs_dac_power_off,
+ .owner = THIS_MODULE,
+};
+
+static u8 phy_meson_cvbs_trimming_version(u8 trimming1)
+{
+ if ((trimming1 & 0xf0) == 0xa0)
+ return 5;
+ else if ((trimming1 & 0xf0) == 0x40)
+ return 2;
+ else if ((trimming1 & 0xc0) == 0x80)
+ return 1;
+ else if ((trimming1 & 0xc0) == 0x00)
+ return 0;
+ else
+ return 0xff;
+}
+
+static int phy_meson_cvbs_read_trimming(struct device *dev,
+ struct phy_meson_cvbs_dac_priv *priv)
+{
+ struct nvmem_cell *cell;
+ u8 *trimming;
+ size_t len;
+
+ cell = devm_nvmem_cell_get(dev, "cvbs_trimming");
+ if (IS_ERR(cell))
+ return dev_err_probe(dev, PTR_ERR(cell),
+ "Failed to get the 'cvbs_trimming' nvmem-cell\n");
+
+ trimming = nvmem_cell_read(cell, &len);
+ if (IS_ERR(trimming)) {
+ /*
+ * TrustZone firmware may block access to the CVBS trimming
+ * data stored in the eFuse. On those devices the trimming data
+ * is stored in the u-boot environment. However, the known
+ * examples of trimming data in the u-boot environment are all
+ * zero.
+ */
+ dev_dbg(dev,
+ "Failed to read the 'cvbs_trimming' nvmem-cell - falling back to a default value\n");
+ priv->cdac_gsw_enable_val = 0x0;
+ return 0;
+ }
+
+ if (len != 2) {
+ kfree(trimming);
+ return dev_err_probe(dev, -EINVAL,
+ "Read the 'cvbs_trimming' nvmem-cell with invalid length\n");
+ }
+
+ switch (phy_meson_cvbs_trimming_version(trimming[1])) {
+ case 1:
+ case 2:
+ case 5:
+ priv->cdac_gsw_enable_val = trimming[0] & 0x7;
+ break;
+ default:
+ priv->cdac_gsw_enable_val = 0x0;
+ break;
+ }
+
+ kfree(trimming);
+
+ return 0;
+}
+
+static int phy_meson_cvbs_dac_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct phy_meson_cvbs_dac_priv *priv;
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct regmap *hhi;
+ struct phy *phy;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ if (np) {
+ priv->data = device_get_match_data(dev);
+ if (!priv->data)
+ return dev_err_probe(dev, -EINVAL,
+ "Could not find the OF match data\n");
+
+ hhi = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(hhi))
+ return dev_err_probe(dev, PTR_ERR(hhi),
+ "Failed to get the parent syscon\n");
+ } else {
+ const struct platform_device_id *pdev_id;
+
+ pdev_id = platform_get_device_id(pdev);
+ if (!pdev_id)
+ return dev_err_probe(dev, -EINVAL,
+ "Failed to find platform device ID\n");
+
+ priv->data = (void *)pdev_id->driver_data;
+ if (!priv->data)
+ return dev_err_probe(dev, -EINVAL,
+ "Could not find the platform driver data\n");
+
+ hhi = syscon_regmap_lookup_by_compatible("amlogic,meson-gx-hhi-sysctrl");
+ if (IS_ERR(hhi))
+ return dev_err_probe(dev, PTR_ERR(hhi),
+ "Failed to get the \"amlogic,meson-gx-hhi-sysctrl\" syscon\n");
+ }
+
+ if (priv->data->needs_cvbs_trimming_nvmem_cell) {
+ ret = phy_meson_cvbs_read_trimming(dev, priv);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_regmap_field_bulk_alloc(dev, hhi, priv->regs,
+ priv->data->reg_fields,
+ MESON_CVBS_DAC_NUM_REGS);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to create regmap fields\n");
+
+ phy = devm_phy_create(dev, np, &phy_meson_cvbs_dac_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ phy_set_drvdata(phy, priv);
+
+ if (np) {
+ phy_provider = devm_of_phy_provider_register(dev,
+ of_phy_simple_xlate);
+ ret = PTR_ERR_OR_ZERO(phy_provider);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register PHY provider\n");
+ } else {
+ platform_set_drvdata(pdev, phy);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id phy_meson_cvbs_dac_of_match[] = {
+ {
+ .compatible = "amlogic,meson8-cvbs-dac",
+ .data = &phy_meson8_cvbs_dac_data,
+ },
+ {
+ .compatible = "amlogic,meson8b-cvbs-dac",
+ .data = &phy_meson8_cvbs_dac_data,
+ },
+ {
+ .compatible = "amlogic,meson-gxbb-cvbs-dac",
+ .data = &phy_meson_gxbb_cvbs_dac_data,
+ },
+ {
+ .compatible = "amlogic,meson-gxl-cvbs-dac",
+ .data = &phy_meson_gxl_cvbs_dac_data,
+ },
+ {
+ .compatible = "amlogic,meson-g12a-cvbs-dac",
+ .data = &phy_meson_g12a_cvbs_dac_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, phy_meson_cvbs_dac_of_match);
+
+/*
+ * The platform_device_id table is used for backwards compatibility with old
+ * .dtbs which don't have a CVBS DAC node (where the VPU DRM driver registers
+ * this as a platform device. Support for additional SoCs should only be added
+ * to the of_device_id table above.
+ */
+static const struct platform_device_id phy_meson_cvbs_dac_device_ids[] = {
+ {
+ .name = "meson-gxbb-cvbs-dac",
+ .driver_data = (kernel_ulong_t)&phy_meson_gxbb_cvbs_dac_data,
+ },
+ {
+ .name = "meson-gxl-cvbs-dac",
+ .driver_data = (kernel_ulong_t)&phy_meson_gxl_cvbs_dac_data,
+ },
+ {
+ .name = "meson-g12a-cvbs-dac",
+ .driver_data = (kernel_ulong_t)&phy_meson_g12a_cvbs_dac_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, phy_meson_cvbs_dac_device_ids);
+
+static struct platform_driver phy_meson_cvbs_dac_driver = {
+ .driver = {
+ .name = "phy-meson-cvbs-dac",
+ .of_match_table = phy_meson_cvbs_dac_of_match,
+ },
+ .id_table = phy_meson_cvbs_dac_device_ids,
+ .probe = phy_meson_cvbs_dac_probe,
+};
+module_platform_driver(phy_meson_cvbs_dac_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Amlogic Meson CVBS DAC driver");
+MODULE_LICENSE("GPL v2");
--
Armbian