Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1677602 > unrolled thread
| Started by | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| First post | 2017-06-29 12:50 +0200 |
| Last post | 2017-07-04 16:10 +0200 |
| Articles | 11 — 3 participants |
Back to article view | Back to linux.kernel
[PATCH v5 0/4] Synopsys Designware HDMI Video Capture Controller + PHY Jose Abreu <Jose.Abreu@synopsys.com> - 2017-06-29 12:50 +0200
[PATCH v5 3/4] MAINTAINERS: Add entry for Synopsys Designware HDMI drivers Jose Abreu <Jose.Abreu@synopsys.com> - 2017-06-29 12:50 +0200
[PATCH v5 1/4] [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver Jose Abreu <Jose.Abreu@synopsys.com> - 2017-06-29 12:50 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu <Jose.Abreu@synopsys.com> - 2017-07-03 12:00 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Hans Verkuil <hverkuil@xs4all.nl> - 2017-07-03 12:40 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu <Jose.Abreu@synopsys.com> - 2017-07-04 11:30 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Hans Verkuil <hansverk@cisco.com> - 2017-07-04 11:50 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu <Jose.Abreu@synopsys.com> - 2017-07-04 14:40 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Hans Verkuil <hansverk@cisco.com> - 2017-07-04 15:10 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu <Jose.Abreu@synopsys.com> - 2017-07-04 16:00 +0200
Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Hans Verkuil <hansverk@cisco.com> - 2017-07-04 16:10 +0200
| From | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| Date | 2017-06-29 12:50 +0200 |
| Subject | [PATCH v5 0/4] Synopsys Designware HDMI Video Capture Controller + PHY |
| Message-ID | <tXFbj-6tm-5@gated-at.bofh.it> |
The Synopsys Designware HDMI RX controller is an HDMI receiver controller that is responsible to process digital data that comes from a phy. The final result is a stream of raw video data that can then be connected to a video DMA, for example, and transfered into RAM so that it can be displayed. The controller + phy available in this series natively support all HDMI 1.4 and HDMI 2.0 modes, including deep color. Although, the driver is quite in its initial stage and unfortunatelly only non deep color modes are supported. Also, audio is not yet supported in the driver (the controller has several audio output interfaces). Version 5 addresses review comments from Sylwester Nawrocki and Rob Herring regarding device tree bindings, messages printing levels, sleep functions and uses the new v4l2_async_subnotifier_register() function. This series depends on the patch at [1]. This series was tested in a FPGA platform. Jose Abreu (4): [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver [media] platform: Add Synopsys Designware HDMI RX Controller Driver MAINTAINERS: Add entry for Synopsys Designware HDMI drivers dt-bindings: media: Document Synopsys Designware HDMI RX Cc: Carlos Palminha <palminha@synopsys.com> Cc: Mauro Carvalho Chehab <mchehab@kernel.org> Cc: Hans Verkuil <hans.verkuil@cisco.com> Cc: Rob Herring <robh+dt@kernel.org> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Sylwester Nawrocki <snawrocki@kernel.org> [1] https://patchwork.linuxtv.org/patch/41834/ .../devicetree/bindings/media/snps,dw-hdmi-rx.txt | 70 + MAINTAINERS | 7 + drivers/media/platform/Kconfig | 2 + drivers/media/platform/Makefile | 2 + drivers/media/platform/dwc/Kconfig | 23 + drivers/media/platform/dwc/Makefile | 2 + drivers/media/platform/dwc/dw-hdmi-phy-e405.c | 844 +++++++++ drivers/media/platform/dwc/dw-hdmi-phy-e405.h | 63 + drivers/media/platform/dwc/dw-hdmi-rx.c | 1824 ++++++++++++++++++++ drivers/media/platform/dwc/dw-hdmi-rx.h | 441 +++++ include/media/dwc/dw-hdmi-phy-pdata.h | 128 ++ include/media/dwc/dw-hdmi-rx-pdata.h | 97 ++ 12 files changed, 3503 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt create mode 100644 drivers/media/platform/dwc/Kconfig create mode 100644 drivers/media/platform/dwc/Makefile create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.c create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.h create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h create mode 100644 include/media/dwc/dw-hdmi-phy-pdata.h create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h -- 1.9.1
[toc] | [next] | [standalone]
| From | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| Date | 2017-06-29 12:50 +0200 |
| Subject | [PATCH v5 3/4] MAINTAINERS: Add entry for Synopsys Designware HDMI drivers |
| Message-ID | <tXFbk-6tm-21@gated-at.bofh.it> |
| In reply to | #1677602 |
Add a entry for Synopsys Designware HDMI Receivers drivers and phys. Signed-off-by: Jose Abreu <joabreu@synopsys.com> Cc: Carlos Palminha <palminha@synopsys.com> --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c4be6d4..7ebc6dd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11354,6 +11354,13 @@ L: netdev@vger.kernel.org S: Supported F: drivers/net/ethernet/synopsys/ +SYNOPSYS DESIGNWARE HDMI RECEIVERS AND PHY DRIVERS +M: Jose Abreu <joabreu@synopsys.com> +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/media/platform/dwc/* +F: include/media/dwc/* + SYNOPSYS DESIGNWARE I2C DRIVER M: Jarkko Nikula <jarkko.nikula@linux.intel.com> R: Andy Shevchenko <andriy.shevchenko@linux.intel.com> -- 1.9.1
[toc] | [prev] | [next] | [standalone]
| From | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| Date | 2017-06-29 12:50 +0200 |
| Subject | [PATCH v5 1/4] [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver |
| Message-ID | <tXFbk-6tm-23@gated-at.bofh.it> |
| In reply to | #1677602 |
This adds support for the Synopsys Designware HDMI RX PHY e405. This
phy receives and decodes HDMI video that is delivered to a controller.
Main features included in this driver are:
- Equalizer algorithm that chooses the phy best settings
according to the detected HDMI cable characteristics.
- Support for scrambling
- Support for HDMI 2.0 modes up to 6G (HDMI 4k@60Hz).
The driver was implemented as a standalone V4L2 subdevice and the
phy interface with the controller was implemented using V4L2 ioctls. I
do not know if this is the best option but it is not possible to use the
existing API functions directly as we need specific functions that will
be called by the controller at specific configuration stages.
There is also a bidirectional communication between controller and phy:
The phy must provide functions that the controller will call (i.e.
configuration functions) and the controller must provide read/write
callbacks, as well as other specific functions.
Signed-off-by: Jose Abreu <joabreu@synopsys.com>
Cc: Carlos Palminha <palminha@synopsys.com>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: Hans Verkuil <hans.verkuil@cisco.com>
Cc: Sylwester Nawrocki <snawrocki@kernel.org>
Changes from v4:
- Use usleep_range (Sylwester)
- Remove some comments (Sylwester)
- Parse phy version from of_device_id (Sylwester)
- Use "cfg" instead of "cfg-clk" (Sylwester, Rob)
- Change some messages to dev_dbg (Sylwester)
Changes from v3:
- Use v4l2 async API (Sylwester)
- Use clock API (Sylwester)
- Add compatible string (Sylwester)
Changes from RFC:
- Remove a bunch of functions that can be collapsed into
a single config() function
- Add comments for the callbacks and structures (Hans)
---
drivers/media/platform/Kconfig | 2 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/dwc/Kconfig | 8 +
drivers/media/platform/dwc/Makefile | 1 +
drivers/media/platform/dwc/dw-hdmi-phy-e405.c | 844 ++++++++++++++++++++++++++
drivers/media/platform/dwc/dw-hdmi-phy-e405.h | 63 ++
include/media/dwc/dw-hdmi-phy-pdata.h | 128 ++++
7 files changed, 1048 insertions(+)
create mode 100644 drivers/media/platform/dwc/Kconfig
create mode 100644 drivers/media/platform/dwc/Makefile
create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.c
create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.h
create mode 100644 include/media/dwc/dw-hdmi-phy-pdata.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 1313cd5..47d4a50 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -33,6 +33,8 @@ source "drivers/media/platform/omap/Kconfig"
source "drivers/media/platform/blackfin/Kconfig"
+source "drivers/media/platform/dwc/Kconfig"
+
config VIDEO_SH_VOU
tristate "SuperH VOU video output driver"
depends on MEDIA_CAMERA_SUPPORT
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 9beadc7..e6a55fb 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -86,3 +86,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
+
+obj-y += dwc/
diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
new file mode 100644
index 0000000..361d38d
--- /dev/null
+++ b/drivers/media/platform/dwc/Kconfig
@@ -0,0 +1,8 @@
+config VIDEO_DWC_HDMI_PHY_E405
+ tristate "Synopsys Designware HDMI RX PHY e405 driver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ help
+ Support for Synopsys Designware HDMI RX PHY. Version is e405.
+
+ To compile this driver as a module, choose M here. The module
+ will be called dw-hdmi-phy-e405.
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
new file mode 100644
index 0000000..fc3b62c
--- /dev/null
+++ b/drivers/media/platform/dwc/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
diff --git a/drivers/media/platform/dwc/dw-hdmi-phy-e405.c b/drivers/media/platform/dwc/dw-hdmi-phy-e405.c
new file mode 100644
index 0000000..26d70ca
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-phy-e405.c
@@ -0,0 +1,844 @@
+/*
+ * Synopsys Designware HDMI PHY E405 driver
+ *
+ * This Synopsys dw-phy-e405 software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE
+ * ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-subdev.h>
+#include <media/dwc/dw-hdmi-phy-pdata.h>
+#include "dw-hdmi-phy-e405.h"
+
+MODULE_AUTHOR("Jose Abreu <joabreu@synopsys.com>");
+MODULE_DESCRIPTION("Designware HDMI PHY e405 driver");
+MODULE_LICENSE("Dual MIT/GPL");
+
+#define PHY_EQ_WAIT_TIME_START 3
+#define PHY_EQ_SLEEP_TIME_CDR 30
+#define PHY_EQ_SLEEP_TIME_ACQ 1
+#define PHY_EQ_BOUNDSPREAD 20
+#define PHY_EQ_MIN_ACQ_STABLE 3
+#define PHY_EQ_ACC_LIMIT 360
+#define PHY_EQ_ACC_MIN_LIMIT 0
+#define PHY_EQ_MAX_SETTING 13
+#define PHY_EQ_SHORT_CABLE_SETTING 4
+#define PHY_EQ_ERROR_CABLE_SETTING 4
+#define PHY_EQ_MIN_SLOPE 50
+#define PHY_EQ_AVG_ACQ 5
+#define PHY_EQ_MINMAX_NTRIES 3
+#define PHY_EQ_EQUALIZED_COUNTER_VAL 512
+#define PHY_EQ_EQUALIZED_COUNTER_VAL_HDMI20 512
+#define PHY_EQ_MINMAX_MAXDIFF 4
+#define PHY_EQ_MINMAX_MAXDIFF_HDMI20 2
+#define PHY_EQ_FATBIT_MASK 0x0000
+#define PHY_EQ_FATBIT_MASK_4K 0x0c03
+#define PHY_EQ_FATBIT_MASK_HDMI20 0x0e03
+
+struct dw_phy_eq_ch {
+ u16 best_long_setting;
+ u8 valid_long_setting;
+ u16 best_short_setting;
+ u8 valid_short_setting;
+ u16 best_setting;
+ u16 acc;
+ u16 acq;
+ u16 last_acq;
+ u16 upper_bound_acq;
+ u16 lower_bound_acq;
+ u16 out_bound_acq;
+ u16 read_acq;
+};
+
+static const struct dw_phy_mpll_config {
+ u16 addr;
+ u16 val;
+} dw_phy_e405_mpll_cfg[] = {
+ { 0x27, 0x1B94 },
+ { 0x28, 0x16D2 },
+ { 0x29, 0x12D9 },
+ { 0x2A, 0x3249 },
+ { 0x2B, 0x3653 },
+ { 0x2C, 0x3436 },
+ { 0x2D, 0x124D },
+ { 0x2E, 0x0001 },
+ { 0xCE, 0x0505 },
+ { 0xCF, 0x0505 },
+ { 0xD0, 0x0000 },
+ { 0x00, 0x0000 },
+};
+
+struct dw_phy_dev {
+ struct device *dev;
+ struct dw_phy_pdata *config;
+ unsigned int version;
+ struct clk *clk;
+ u16 cfg_clk;
+ bool phy_enabled;
+ struct v4l2_subdev sd;
+ u16 mpll_status;
+ unsigned char color_depth;
+ bool hdmi2;
+ bool scrambling;
+ struct mutex lock;
+};
+
+static inline struct dw_phy_dev *to_dw_dev(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct dw_phy_dev, sd);
+}
+
+static void phy_write(struct dw_phy_dev *dw_dev, u16 val, u16 addr)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ dw_dev->config->funcs->write(arg, val, addr);
+}
+
+static u16 phy_read(struct dw_phy_dev *dw_dev, u16 addr)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ return dw_dev->config->funcs->read(arg, addr);
+}
+
+static void phy_reset(struct dw_phy_dev *dw_dev, int enable)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ dw_dev->config->funcs->reset(arg, enable);
+}
+
+static void phy_pddq(struct dw_phy_dev *dw_dev, int enable)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ dw_dev->config->funcs->pddq(arg, enable);
+}
+
+static void phy_svsmode(struct dw_phy_dev *dw_dev, int enable)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ dw_dev->config->funcs->svsmode(arg, enable);
+}
+
+static void phy_zcal_reset(struct dw_phy_dev *dw_dev)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ dw_dev->config->funcs->zcal_reset(arg);
+}
+
+static bool phy_zcal_done(struct dw_phy_dev *dw_dev)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ return dw_dev->config->funcs->zcal_done(arg);
+}
+
+static bool phy_tmds_valid(struct dw_phy_dev *dw_dev)
+{
+ void *arg = dw_dev->config->funcs_arg;
+
+ return dw_dev->config->funcs->tmds_valid(arg);
+}
+
+static int dw_phy_eq_test(struct dw_phy_dev *dw_dev,
+ u16 *fat_bit_mask, int *min_max_length)
+{
+ u16 main_fsm_status, val;
+ int i;
+
+ for (i = 0; i < PHY_EQ_WAIT_TIME_START; i++) {
+ main_fsm_status = phy_read(dw_dev, PHY_MAINFSM_STATUS1);
+ if (main_fsm_status & 0x100)
+ break;
+ mdelay(PHY_EQ_SLEEP_TIME_CDR);
+ }
+
+ if (i == PHY_EQ_WAIT_TIME_START) {
+ dev_dbg(dw_dev->dev, "phy start conditions not achieved\n");
+ return -ETIMEDOUT;
+ }
+
+ if (main_fsm_status & 0x400) {
+ dev_dbg(dw_dev->dev, "invalid pll rate\n");
+ return -EINVAL;
+ }
+
+ val = (phy_read(dw_dev, PHY_CDR_CTRL_CNT) & 0x300) >> 8;
+ if (val == 0x1) {
+ /* HDMI 2.0 */
+ *fat_bit_mask = PHY_EQ_FATBIT_MASK_HDMI20;
+ *min_max_length = PHY_EQ_MINMAX_MAXDIFF_HDMI20;
+ dev_dbg(dw_dev->dev, "[EQUALIZER] using HDMI 2.0 values\n");
+ } else if (!(main_fsm_status & 0x600)) {
+ /* HDMI 1.4 (pll rate = 0) */
+ *fat_bit_mask = PHY_EQ_FATBIT_MASK_4K;
+ *min_max_length = PHY_EQ_MINMAX_MAXDIFF;
+ dev_dbg(dw_dev->dev, "[EQUALIZER] using HDMI 1.4@4k values\n");
+ } else {
+ /* HDMI 1.4 */
+ *fat_bit_mask = PHY_EQ_FATBIT_MASK;
+ *min_max_length = PHY_EQ_MINMAX_MAXDIFF;
+ dev_dbg(dw_dev->dev, "[EQUALIZER] using HDMI 1.4 values\n");
+ }
+
+ return 0;
+}
+
+static void dw_phy_eq_default(struct dw_phy_dev *dw_dev)
+{
+ phy_write(dw_dev, 0x08A8, PHY_CH0_EQ_CTRL1);
+ phy_write(dw_dev, 0x0020, PHY_CH0_EQ_CTRL2);
+ phy_write(dw_dev, 0x08A8, PHY_CH1_EQ_CTRL1);
+ phy_write(dw_dev, 0x0020, PHY_CH1_EQ_CTRL2);
+ phy_write(dw_dev, 0x08A8, PHY_CH2_EQ_CTRL1);
+ phy_write(dw_dev, 0x0020, PHY_CH2_EQ_CTRL2);
+}
+
+static void dw_phy_eq_single(struct dw_phy_dev *dw_dev)
+{
+ phy_write(dw_dev, 0x0211, PHY_CH0_EQ_CTRL1);
+ phy_write(dw_dev, 0x0211, PHY_CH1_EQ_CTRL1);
+ phy_write(dw_dev, 0x0211, PHY_CH2_EQ_CTRL1);
+}
+
+static void dw_phy_eq_equal_setting(struct dw_phy_dev *dw_dev,
+ u16 lock_vector)
+{
+ phy_write(dw_dev, lock_vector, PHY_CH0_EQ_CTRL4);
+ phy_write(dw_dev, 0x0024, PHY_CH0_EQ_CTRL2);
+ phy_write(dw_dev, 0x0026, PHY_CH0_EQ_CTRL2);
+ phy_read(dw_dev, PHY_CH0_EQ_STATUS2);
+ phy_write(dw_dev, lock_vector, PHY_CH1_EQ_CTRL4);
+ phy_write(dw_dev, 0x0024, PHY_CH1_EQ_CTRL2);
+ phy_write(dw_dev, 0x0026, PHY_CH1_EQ_CTRL2);
+ phy_read(dw_dev, PHY_CH1_EQ_STATUS2);
+ phy_write(dw_dev, lock_vector, PHY_CH2_EQ_CTRL4);
+ phy_write(dw_dev, 0x0024, PHY_CH2_EQ_CTRL2);
+ phy_write(dw_dev, 0x0026, PHY_CH2_EQ_CTRL2);
+ phy_read(dw_dev, PHY_CH2_EQ_STATUS2);
+}
+
+static void dw_phy_eq_equal_setting_ch0(struct dw_phy_dev *dw_dev,
+ u16 lock_vector)
+{
+ phy_write(dw_dev, lock_vector, PHY_CH0_EQ_CTRL4);
+ phy_write(dw_dev, 0x0024, PHY_CH0_EQ_CTRL2);
+ phy_write(dw_dev, 0x0026, PHY_CH0_EQ_CTRL2);
+ phy_read(dw_dev, PHY_CH0_EQ_STATUS2);
+}
+
+static void dw_phy_eq_equal_setting_ch1(struct dw_phy_dev *dw_dev,
+ u16 lock_vector)
+{
+ phy_write(dw_dev, lock_vector, PHY_CH1_EQ_CTRL4);
+ phy_write(dw_dev, 0x0024, PHY_CH1_EQ_CTRL2);
+ phy_write(dw_dev, 0x0026, PHY_CH1_EQ_CTRL2);
+ phy_read(dw_dev, PHY_CH1_EQ_STATUS2);
+}
+
+static void dw_phy_eq_equal_setting_ch2(struct dw_phy_dev *dw_dev,
+ u16 lock_vector)
+{
+ phy_write(dw_dev, lock_vector, PHY_CH2_EQ_CTRL4);
+ phy_write(dw_dev, 0x0024, PHY_CH2_EQ_CTRL2);
+ phy_write(dw_dev, 0x0026, PHY_CH2_EQ_CTRL2);
+ phy_read(dw_dev, PHY_CH2_EQ_STATUS2);
+}
+
+static void dw_phy_eq_auto_calib(struct dw_phy_dev *dw_dev)
+{
+ phy_write(dw_dev, 0x1809, PHY_MAINFSM_CTRL);
+ phy_write(dw_dev, 0x1819, PHY_MAINFSM_CTRL);
+ phy_write(dw_dev, 0x1809, PHY_MAINFSM_CTRL);
+}
+
+static void dw_phy_eq_init_vars(struct dw_phy_eq_ch *ch)
+{
+ ch->acc = 0;
+ ch->acq = 0;
+ ch->last_acq = 0;
+ ch->valid_long_setting = 0;
+ ch->valid_short_setting = 0;
+ ch->best_setting = PHY_EQ_SHORT_CABLE_SETTING;
+}
+
+static bool dw_phy_eq_acquire_early_cnt(struct dw_phy_dev *dw_dev,
+ u16 setting, u16 acq, struct dw_phy_eq_ch *ch0,
+ struct dw_phy_eq_ch *ch1, struct dw_phy_eq_ch *ch2)
+{
+ u16 lock_vector = 0x1;
+ int i;
+
+ lock_vector <<= setting;
+ ch0->out_bound_acq = 0;
+ ch1->out_bound_acq = 0;
+ ch2->out_bound_acq = 0;
+ ch0->acq = 0;
+ ch1->acq = 0;
+ ch2->acq = 0;
+
+ dw_phy_eq_equal_setting(dw_dev, lock_vector);
+ dw_phy_eq_auto_calib(dw_dev);
+
+ mdelay(PHY_EQ_SLEEP_TIME_CDR);
+ if (!phy_tmds_valid(dw_dev))
+ dev_dbg(dw_dev->dev, "TMDS is NOT valid\n");
+
+ ch0->read_acq = phy_read(dw_dev, PHY_CH0_EQ_STATUS3);
+ ch1->read_acq = phy_read(dw_dev, PHY_CH1_EQ_STATUS3);
+ ch2->read_acq = phy_read(dw_dev, PHY_CH2_EQ_STATUS3);
+
+ ch0->acq += ch0->read_acq;
+ ch1->acq += ch1->read_acq;
+ ch2->acq += ch2->read_acq;
+
+ ch0->upper_bound_acq = ch0->read_acq + PHY_EQ_BOUNDSPREAD;
+ ch0->lower_bound_acq = ch0->read_acq - PHY_EQ_BOUNDSPREAD;
+ ch1->upper_bound_acq = ch1->read_acq + PHY_EQ_BOUNDSPREAD;
+ ch1->lower_bound_acq = ch1->read_acq - PHY_EQ_BOUNDSPREAD;
+ ch2->upper_bound_acq = ch2->read_acq + PHY_EQ_BOUNDSPREAD;
+ ch2->lower_bound_acq = ch2->read_acq - PHY_EQ_BOUNDSPREAD;
+
+ for (i = 1; i < acq; i++) {
+ dw_phy_eq_auto_calib(dw_dev);
+ mdelay(PHY_EQ_SLEEP_TIME_ACQ);
+
+ if ((ch0->read_acq > ch0->upper_bound_acq) ||
+ (ch0->read_acq < ch0->lower_bound_acq))
+ ch0->out_bound_acq++;
+ if ((ch1->read_acq > ch1->upper_bound_acq) ||
+ (ch1->read_acq < ch1->lower_bound_acq))
+ ch1->out_bound_acq++;
+ if ((ch2->read_acq > ch2->upper_bound_acq) ||
+ (ch2->read_acq < ch1->lower_bound_acq))
+ ch2->out_bound_acq++;
+
+ if (i == PHY_EQ_MIN_ACQ_STABLE) {
+ if ((ch0->out_bound_acq == 0) &&
+ (ch1->out_bound_acq == 0) &&
+ (ch2->out_bound_acq == 0)) {
+ acq = 3;
+ break;
+ }
+ }
+
+ ch0->read_acq = phy_read(dw_dev, PHY_CH0_EQ_STATUS3);
+ ch1->read_acq = phy_read(dw_dev, PHY_CH1_EQ_STATUS3);
+ ch2->read_acq = phy_read(dw_dev, PHY_CH2_EQ_STATUS3);
+
+ ch0->acq += ch0->read_acq;
+ ch1->acq += ch1->read_acq;
+ ch2->acq += ch2->read_acq;
+ }
+
+ ch0->acq = ch0->acq / acq;
+ ch1->acq = ch1->acq / acq;
+ ch2->acq = ch2->acq / acq;
+
+ return true;
+}
+
+static int dw_phy_eq_test_type(u16 setting, bool tmds_valid,
+ struct dw_phy_eq_ch *ch)
+{
+ u16 step_slope = 0;
+
+ if ((ch->acq < ch->last_acq) && tmds_valid) {
+ /* Long cable equalization */
+ ch->acc += ch->last_acq - ch->acq;
+ if ((ch->valid_long_setting == 0) && (ch->acq < 512) &&
+ (ch->acc > 0)) {
+ ch->best_long_setting = setting;
+ ch->valid_long_setting = 1;
+ }
+ step_slope = ch->last_acq - ch->acq;
+ }
+
+ if (tmds_valid && (ch->valid_short_setting == 0)) {
+ /* Short cable equalization */
+ if ((setting < PHY_EQ_SHORT_CABLE_SETTING) &&
+ (ch->acq < PHY_EQ_EQUALIZED_COUNTER_VAL)) {
+ ch->best_short_setting= setting;
+ ch->valid_short_setting = 1;
+ }
+
+ if (setting == PHY_EQ_SHORT_CABLE_SETTING) {
+ ch->best_short_setting = PHY_EQ_SHORT_CABLE_SETTING;
+ ch->valid_short_setting = 1;
+ }
+ }
+
+ if (ch->valid_long_setting && (ch->acc > PHY_EQ_ACC_LIMIT)) {
+ ch->best_setting = ch->best_long_setting;
+ return 1;
+ }
+
+ if ((setting == PHY_EQ_MAX_SETTING) && (ch->acc < PHY_EQ_ACC_LIMIT) &&
+ ch->valid_short_setting) {
+ ch->best_setting = ch->best_short_setting;
+ return 2;
+ }
+
+ if ((setting == PHY_EQ_MAX_SETTING) && tmds_valid &&
+ (ch->acc > PHY_EQ_ACC_LIMIT) &&
+ (step_slope > PHY_EQ_MIN_SLOPE)) {
+ ch->best_setting = PHY_EQ_MAX_SETTING;
+ return 3;
+ }
+
+ if (setting == PHY_EQ_MAX_SETTING) {
+ ch->best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+ return 255;
+ }
+
+ return 0;
+}
+
+static bool dw_phy_eq_setting_finder(struct dw_phy_dev *dw_dev, u16 acq,
+ struct dw_phy_eq_ch *ch0, struct dw_phy_eq_ch *ch1,
+ struct dw_phy_eq_ch *ch2)
+{
+ u16 act = 0;
+ int ret_ch0 = 0, ret_ch1 = 0, ret_ch2 = 0;
+ bool tmds_valid = false;
+
+ dw_phy_eq_init_vars(ch0);
+ dw_phy_eq_init_vars(ch1);
+ dw_phy_eq_init_vars(ch2);
+
+ tmds_valid = dw_phy_eq_acquire_early_cnt(dw_dev, act, acq,
+ ch0, ch1, ch2);
+
+ while ((ret_ch0 == 0) || (ret_ch1 == 0) || (ret_ch2 == 0)) {
+ act++;
+
+ ch0->last_acq = ch0->acq;
+ ch1->last_acq = ch1->acq;
+ ch2->last_acq = ch2->acq;
+
+ tmds_valid = dw_phy_eq_acquire_early_cnt(dw_dev, act, acq,
+ ch0, ch1, ch2);
+
+ if (!ret_ch0)
+ ret_ch0 = dw_phy_eq_test_type(act, tmds_valid, ch0);
+ if (!ret_ch1)
+ ret_ch1 = dw_phy_eq_test_type(act, tmds_valid, ch1);
+ if (!ret_ch2)
+ ret_ch2 = dw_phy_eq_test_type(act, tmds_valid, ch2);
+ }
+
+ if ((ret_ch0 == 255) || (ret_ch1 == 255) || (ret_ch2 == 255))
+ return false;
+ return true;
+}
+
+static bool dw_phy_eq_maxvsmin(u16 ch0_setting, u16 ch1_setting,
+ u16 ch2_setting, u16 min_max_length)
+{
+ u16 min = ch0_setting, max = ch0_setting;
+
+ if (ch1_setting > max)
+ max = ch1_setting;
+ if (ch2_setting > max)
+ max = ch2_setting;
+ if (ch1_setting < min)
+ min = ch1_setting;
+ if (ch2_setting < min)
+ min = ch2_setting;
+
+ if ((max - min) > min_max_length)
+ return false;
+ return true;
+}
+
+static int dw_phy_eq_init(struct dw_phy_dev *dw_dev, u16 acq, bool force)
+{
+ struct dw_phy_eq_ch ch0, ch1, ch2;
+ u16 fat_bit_mask, lock_vector = 0x1;
+ int min_max_length, i, ret = 0;
+ u16 mpll_status;
+
+ if (dw_dev->version < 401)
+ return ret;
+ if (!dw_dev->phy_enabled)
+ return -EINVAL;
+
+ mpll_status = phy_read(dw_dev, PHY_CLK_MPLL_STATUS);
+ if (mpll_status == dw_dev->mpll_status && !force)
+ return ret;
+ dw_dev->mpll_status = mpll_status;
+
+ phy_write(dw_dev, 0x00, PHY_MAINFSM_OVR2);
+ phy_write(dw_dev, 0x00, PHY_CH0_EQ_CTRL3);
+ phy_write(dw_dev, 0x00, PHY_CH1_EQ_CTRL3);
+ phy_write(dw_dev, 0x00, PHY_CH2_EQ_CTRL3);
+
+ ret = dw_phy_eq_test(dw_dev, &fat_bit_mask, &min_max_length);
+ if (ret) {
+ if (ret == -EINVAL) /* Means equalizer is not needed */
+ ret = 0;
+
+ /* Do not change values if we don't have clock */
+ if (ret != -ETIMEDOUT) {
+ dw_phy_eq_default(dw_dev);
+ phy_pddq(dw_dev, 1);
+ phy_pddq(dw_dev, 0);
+ }
+ } else {
+ dw_phy_eq_single(dw_dev);
+ dw_phy_eq_equal_setting(dw_dev, 0x0001);
+ phy_write(dw_dev, fat_bit_mask, PHY_CH0_EQ_CTRL6);
+ phy_write(dw_dev, fat_bit_mask, PHY_CH1_EQ_CTRL6);
+ phy_write(dw_dev, fat_bit_mask, PHY_CH2_EQ_CTRL6);
+
+ for (i = 0; i < PHY_EQ_MINMAX_NTRIES; i++) {
+ if (dw_phy_eq_setting_finder(dw_dev, acq,
+ &ch0, &ch1, &ch2)) {
+ if (dw_phy_eq_maxvsmin(ch0.best_setting,
+ ch1.best_setting,
+ ch2.best_setting,
+ min_max_length))
+ break;
+ }
+
+ ch0.best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+ ch1.best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+ ch2.best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+ }
+
+ dev_dbg(dw_dev->dev, "equalizer settings: "
+ "ch0=0x%x, ch1=0x%x, ch1=0x%x\n",
+ ch0.best_setting, ch1.best_setting,
+ ch2.best_setting);
+
+ if (i == PHY_EQ_MINMAX_NTRIES)
+ ret = -EINVAL;
+
+ lock_vector = 0x1;
+ lock_vector <<= ch0.best_setting;
+ dw_phy_eq_equal_setting_ch0(dw_dev, lock_vector);
+
+ lock_vector = 0x1;
+ lock_vector <<= ch1.best_setting;
+ dw_phy_eq_equal_setting_ch1(dw_dev, lock_vector);
+
+ lock_vector = 0x1;
+ lock_vector <<= ch2.best_setting;
+ dw_phy_eq_equal_setting_ch2(dw_dev, lock_vector);
+
+ phy_pddq(dw_dev, 1);
+ phy_pddq(dw_dev, 0);
+ }
+
+ return ret;
+}
+
+static int dw_phy_config(struct dw_phy_dev *dw_dev, unsigned char color_depth,
+ bool hdmi2, bool scrambling)
+{
+ const struct dw_phy_mpll_config *mpll_cfg = dw_phy_e405_mpll_cfg;
+ struct device *dev = dw_dev->dev;
+ int timeout = 100;
+ u16 val, res_idx;
+ bool zcal_done;
+
+ dev_dbg(dev, "%s: color_depth=%d, hdmi2=%d, scrambling=%d, cfg_clk=%d\n",
+ __func__, color_depth, hdmi2, scrambling, dw_dev->cfg_clk);
+
+ switch (color_depth) {
+ case 8:
+ res_idx = 0x0;
+ break;
+ case 10:
+ res_idx = 0x1;
+ break;
+ case 12:
+ res_idx = 0x2;
+ break;
+ case 16:
+ res_idx = 0x3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ phy_reset(dw_dev, 1);
+ phy_pddq(dw_dev, 1);
+ phy_svsmode(dw_dev, 1);
+
+ phy_zcal_reset(dw_dev);
+ do {
+ usleep_range(1000, 1100);
+ zcal_done = phy_zcal_done(dw_dev);
+ } while (!zcal_done && timeout--);
+
+ if (!zcal_done) {
+ dev_err(dw_dev->dev, "Zcal calibration failed\n");
+ return -ETIMEDOUT;
+ }
+
+ phy_reset(dw_dev, 0);
+
+ /* CMU */
+ val = (0x08 << 10) | (0x01 << 9);
+ val |= (dw_dev->cfg_clk * 4) & GENMASK(8, 0);
+ phy_write(dw_dev, val, PHY_CMU_CONFIG);
+
+ /* Color Depth and enable fast switching */
+ val = phy_read(dw_dev, PHY_SYSTEM_CONFIG);
+ val = (val & ~0x60) | (res_idx << 5) | BIT(11);
+ phy_write(dw_dev, val, PHY_SYSTEM_CONFIG);
+
+ /* MPLL */
+ for (; mpll_cfg->addr != 0x0; mpll_cfg++)
+ phy_write(dw_dev, mpll_cfg->val, mpll_cfg->addr);
+
+ /* Operation for data rates between 3.4Gbps and 6Gbps */
+ val = phy_read(dw_dev, PHY_CDR_CTRL_CNT);
+ if (hdmi2)
+ val |= BIT(8);
+ else
+ val &= ~BIT(8);
+ phy_write(dw_dev, val, PHY_CDR_CTRL_CNT);
+
+ /* Scrambling */
+ val = phy_read(dw_dev, PHY_OVL_PROT_CTRL);
+ if (scrambling)
+ val |= GENMASK(7,6);
+ else
+ val &= ~GENMASK(7,6);
+ phy_write(dw_dev, val, PHY_OVL_PROT_CTRL);
+
+ /* Enable phy */
+ phy_pddq(dw_dev, 0);
+
+ dw_dev->color_depth = color_depth;
+ dw_dev->hdmi2 = hdmi2;
+ dw_dev->scrambling = scrambling;
+ return 0;
+}
+
+static int dw_phy_enable(struct dw_phy_dev *dw_dev, unsigned char color_depth,
+ bool hdmi2, bool scrambling)
+{
+ int ret;
+
+ if (dw_dev->phy_enabled &&
+ dw_dev->color_depth == color_depth &&
+ dw_dev->hdmi2 == hdmi2 &&
+ dw_dev->scrambling == scrambling)
+ return 0;
+
+ ret = dw_phy_config(dw_dev, color_depth, hdmi2, scrambling);
+ if (ret)
+ return ret;
+
+ phy_reset(dw_dev, 0);
+ phy_pddq(dw_dev, 0);
+ dw_dev->phy_enabled = true;
+ return 0;
+}
+
+static void dw_phy_disable(struct dw_phy_dev *dw_dev)
+{
+ if (!dw_dev->phy_enabled)
+ return;
+
+ phy_reset(dw_dev, 1);
+ phy_pddq(dw_dev, 1);
+ phy_svsmode(dw_dev, 0);
+ dw_dev->mpll_status = 0xFFFF;
+ dw_dev->phy_enabled = false;
+}
+
+static long dw_phy_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
+{
+ struct dw_phy_dev *dw_dev = to_dw_dev(sd);
+ struct dw_phy_config_command *ccmd;
+ struct dw_phy_eq_command *ecmd;
+ int ret = 0;
+
+ dev_dbg(dw_dev->dev, "%s: cmd=%d\n", __func__, cmd);
+
+ mutex_lock(&dw_dev->lock);
+ switch (cmd) {
+ case DW_PHY_IOCTL_EQ_INIT:
+ ecmd = (struct dw_phy_eq_command *)arg;
+ ecmd->result = dw_phy_eq_init(dw_dev, ecmd->nacq, ecmd->force);
+ break;
+ case DW_PHY_IOCTL_CONFIG:
+ ccmd = (struct dw_phy_config_command *)arg;
+ ccmd->result = dw_phy_enable(dw_dev, ccmd->color_depth,
+ ccmd->hdmi2, ccmd->scrambling);
+ break;
+ default:
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+ mutex_unlock(&dw_dev->lock);
+
+ return ret;
+}
+
+static int dw_phy_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct dw_phy_dev *dw_dev = to_dw_dev(sd);
+
+ dev_dbg(dw_dev->dev, "%s: on=%d\n", __func__, on);
+
+ mutex_lock(&dw_dev->lock);
+ if (!on)
+ dw_phy_disable(dw_dev);
+ mutex_unlock(&dw_dev->lock);
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops dw_phy_core_ops = {
+ .ioctl = dw_phy_ioctl,
+ .s_power = dw_phy_s_power,
+};
+
+static const struct v4l2_subdev_ops dw_phy_sd_ops = {
+ .core = &dw_phy_core_ops,
+};
+
+struct dw_hdmi_phy_data {
+ const char *name;
+ unsigned int version;
+};
+
+static const struct dw_hdmi_phy_data dw_phy_e405_data = {
+ .name = "e405",
+ .version = 405,
+};
+
+static int dw_phy_probe(struct platform_device *pdev)
+{
+ struct dw_phy_pdata *pdata = pdev->dev.platform_data;
+ const struct dw_hdmi_phy_data *of_data;
+ struct device *dev = &pdev->dev;
+ struct dw_phy_dev *dw_dev;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ dev_dbg(dev, "probe start\n");
+
+ dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
+ if (!dw_dev)
+ return -ENOMEM;
+
+ if (!pdata) {
+ dev_err(dev, "no platform data suplied\n");
+ return -EINVAL;
+ }
+
+ of_data = of_device_get_match_data(dev);
+ if (!of_data) {
+ dev_err(dev, "no valid phy configuration available\n");
+ return -EINVAL;
+ }
+
+ dw_dev->dev = dev;
+ dw_dev->config = pdata;
+ dw_dev->version = of_data->version;
+ mutex_init(&dw_dev->lock);
+
+ dw_dev->clk = devm_clk_get(dev, "cfg");
+ if (IS_ERR(dw_dev->clk)) {
+ dev_err(dev, "failed to get cfg clock\n");
+ return PTR_ERR(dw_dev->clk);
+ }
+
+ ret = clk_prepare_enable(dw_dev->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable cfg clock\n");
+ return ret;
+ }
+
+ dw_dev->cfg_clk = clk_get_rate(dw_dev->clk) / 1000000U;
+ if (!dw_dev->cfg_clk) {
+ dev_err(dev, "invalid cfg clock frequency\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ /* V4L2 initialization */
+ sd = &dw_dev->sd;
+ v4l2_subdev_init(sd, &dw_phy_sd_ops);
+ strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
+ sd->dev = dev;
+
+ /* Force phy disabling */
+ dw_dev->phy_enabled = true;
+ dw_phy_disable(dw_dev);
+
+ ret = v4l2_async_register_subdev(sd);
+ if (ret) {
+ dev_err(dev, "failed to register subdev\n");
+ goto err_clk;
+ }
+
+ dev_set_drvdata(dev, sd);
+ dev_dbg(dev, "driver probed (name=%s, cfg clock=%d)\n",
+ of_data->name, dw_dev->cfg_clk);
+ return 0;
+
+err_clk:
+ clk_disable_unprepare(dw_dev->clk);
+ return ret;
+}
+
+static int dw_phy_remove(struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev);
+ struct dw_phy_dev *dw_dev = to_dw_dev(sd);
+
+ v4l2_async_unregister_subdev(sd);
+ clk_disable_unprepare(dw_dev->clk);
+ return 0;
+}
+
+static const struct of_device_id dw_hdmi_phy_e405_id[] = {
+ { .compatible = "snps,dw-hdmi-phy-e405", .data = &dw_phy_e405_data, },
+ { },
+};
+MODULE_DEVICE_TABLE(of, dw_hdmi_phy_e405_id);
+
+static struct platform_driver dw_phy_e405_driver = {
+ .probe = dw_phy_probe,
+ .remove = dw_phy_remove,
+ .driver = {
+ .name = DW_PHY_E405_DRVNAME,
+ .of_match_table = dw_hdmi_phy_e405_id,
+ }
+};
+module_platform_driver(dw_phy_e405_driver);
diff --git a/drivers/media/platform/dwc/dw-hdmi-phy-e405.h b/drivers/media/platform/dwc/dw-hdmi-phy-e405.h
new file mode 100644
index 0000000..3e6c8da
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-phy-e405.h
@@ -0,0 +1,63 @@
+/*
+ * Synopsys Designware HDMI PHY E405 driver
+ *
+ * This Synopsys dw-phy-e405 software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE
+ * ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __DW_HDMI_PHY_E405_H__
+#define __DW_HDMI_PHY_E405_H__
+
+#define PHY_CMU_CONFIG 0x02
+#define PHY_SYSTEM_CONFIG 0x03
+#define PHY_MAINFSM_CTRL 0x05
+#define PHY_MAINFSM_OVR2 0x08
+#define PHY_MAINFSM_STATUS1 0x09
+#define PHY_OVL_PROT_CTRL 0x0D
+#define PHY_CDR_CTRL_CNT 0x0E
+#define PHY_CLK_MPLL_STATUS 0x2F
+#define PHY_CH0_EQ_CTRL1 0x32
+#define PHY_CH0_EQ_CTRL2 0x33
+#define PHY_CH0_EQ_STATUS 0x34
+#define PHY_CH0_EQ_CTRL3 0x3E
+#define PHY_CH0_EQ_CTRL4 0x3F
+#define PHY_CH0_EQ_STATUS2 0x40
+#define PHY_CH0_EQ_STATUS3 0x42
+#define PHY_CH0_EQ_CTRL6 0x43
+#define PHY_CH1_EQ_CTRL1 0x52
+#define PHY_CH1_EQ_CTRL2 0x53
+#define PHY_CH1_EQ_STATUS 0x54
+#define PHY_CH1_EQ_CTRL3 0x5E
+#define PHY_CH1_EQ_CTRL4 0x5F
+#define PHY_CH1_EQ_STATUS2 0x60
+#define PHY_CH1_EQ_STATUS3 0x62
+#define PHY_CH1_EQ_CTRL6 0x63
+#define PHY_CH2_EQ_CTRL1 0x72
+#define PHY_CH2_EQ_CTRL2 0x73
+#define PHY_CH2_EQ_STATUS 0x74
+#define PHY_CH2_EQ_CTRL3 0x7E
+#define PHY_CH2_EQ_CTRL4 0x7F
+#define PHY_CH2_EQ_STATUS2 0x80
+#define PHY_CH2_EQ_STATUS3 0x82
+#define PHY_CH2_EQ_CTRL6 0x83
+
+#endif /* __DW_HDMI_PHY_E405_H__ */
diff --git a/include/media/dwc/dw-hdmi-phy-pdata.h b/include/media/dwc/dw-hdmi-phy-pdata.h
new file mode 100644
index 0000000..e533eec
--- /dev/null
+++ b/include/media/dwc/dw-hdmi-phy-pdata.h
@@ -0,0 +1,128 @@
+/*
+ * Synopsys Designware HDMI PHY platform data
+ *
+ * This Synopsys dw-hdmi-phy software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE
+ * ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __DW_HDMI_PHY_PDATA_H__
+#define __DW_HDMI_PHY_PDATA_H__
+
+#define DW_PHY_E405_DRVNAME "dw-hdmi-phy-e405"
+
+#define DW_PHY_IOCTL_EQ_INIT _IOW('R',1,int)
+#define DW_PHY_IOCTL_CONFIG _IOW('R',2,int)
+
+/**
+ * struct dw_phy_eq_command - Command arguments for HDMI PHY equalizer
+ * algorithm.
+ *
+ * @nacq: Number of acquisitions to get.
+ *
+ * @force: Force equalizer algorithm even if the MPLL status didn't change
+ * from previous run.
+ *
+ * @result: Result from the equalizer algorithm. Shall be zero if equalizer
+ * ran with success or didn't run because the video mode does not need
+ * equalizer (for low clock values).
+ */
+struct dw_phy_eq_command {
+ u16 nacq;
+ bool force;
+ int result;
+};
+
+/**
+ * struct dw_phy_config_command - Command arguments for HDMI PHY configuration
+ * function.
+ *
+ * @color_depth: Color depth of the video mode being received.
+ *
+ * @hdmi2: Must be set to true if the video mode being received has a data
+ * rate above 3.4Gbps (generally HDMI 2.0 video modes, like 4k@60Hz).
+ *
+ * @scrambling: Must be set to true if scrambling is currently enabled.
+ * Scrambling is enabled by source when SCDC scrambling_en field is set to 1.
+ *
+ * @result: Result from the configuration function. Shall be zero if phy was
+ * configured with success.
+ */
+struct dw_phy_config_command {
+ unsigned char color_depth;
+ bool hdmi2;
+ bool scrambling;
+ int result;
+};
+
+/**
+ * struct dw_phy_funcs - Set of callbacks used to communicate between phy
+ * and hdmi controller. Controller must correctly fill these callbacks
+ * before probbing the phy driver.
+ *
+ * @write: write callback. Write value 'val' into address 'addr' of phy.
+ *
+ * @read: read callback. Read address 'addr' and return the value.
+ *
+ * @reset: reset callback. Activate phy reset. Active high.
+ *
+ * @pddq: pddq callback. Activate phy configuration mode. Active high.
+ *
+ * @svsmode: svsmode callback. Activate phy retention mode. Active low.
+ *
+ * @zcal_reset: zcal reset callback. Restart the impedance calibration
+ * procedure. Active high. This is only used in prototyping and not in real
+ * ASIC. Callback shall be empty (but non NULL) in ASIC cases.
+ *
+ * @zcal_done: zcal done callback. Return the current status of impedance
+ * calibration procedure. This is only used in prototyping and not in real
+ * ASIC. Shall return always true in ASIC cases.
+ *
+ * @tmds_valid: TMDS valid callback. Return the current status of TMDS signal
+ * that comes from phy and feeds controller. This is read from a controller
+ * register.
+ */
+struct dw_phy_funcs {
+ void (*write) (void *arg, u16 val, u16 addr);
+ u16 (*read) (void *arg, u16 addr);
+ void (*reset) (void *arg, int enable);
+ void (*pddq) (void *arg, int enable);
+ void (*svsmode) (void *arg, int enable);
+ void (*zcal_reset) (void *arg);
+ bool (*zcal_done) (void *arg);
+ bool (*tmds_valid) (void *arg);
+};
+
+/**
+ * struct dw_phy_pdata - Platform data definition for Synopsys HDMI PHY.
+ *
+ * @funcs: set of callbacks that must be correctly filled and supplied to phy.
+ * See @dw_phy_funcs.
+ *
+ * @funcs_arg: parameter that is supplied to callbacks along with the function
+ * parameters.
+ */
+struct dw_phy_pdata {
+ const struct dw_phy_funcs *funcs;
+ void *funcs_arg;
+};
+
+#endif /* __DW_HDMI_PHY_PDATA_H__ */
--
1.9.1
[toc] | [prev] | [next] | [standalone]
| From | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| Date | 2017-07-03 12:00 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZ6j7-7MN-9@gated-at.bofh.it> |
| In reply to | #1677602 |
Hi Hans,
On 03-07-2017 10:27, Hans Verkuil wrote:
> On 06/29/2017 12:46 PM, Jose Abreu wrote:
>> This is an initial submission for the Synopsys Designware HDMI RX
>> Controller Driver. This driver interacts with a phy driver so
>> that
>> a communication between them is created and a video pipeline is
>> configured.
>>
>> The controller + phy pipeline can then be integrated into a fully
>> featured system that can be able to receive video up to 4k@60Hz
>> with deep color 48bit RGB, depending on the platform. Although,
>> this initial version does not yet handle deep color modes.
>>
>> This driver was implemented as a standard V4L2 subdevice and its
>> main features are:
>> - Internal state machine that reconfigures phy until the
>> video is not stable
>> - JTAG communication with phy
>> - Inter-module communication with phy driver
>> - Debug write/read ioctls
>>
>> Some notes:
>> - RX sense controller (cable connection/disconnection) must
>> be handled by the platform wrapper as this is not integrated
>> into the controller RTL
>> - The same goes for EDID ROM's
>> - ZCAL calibration is needed only in FPGA platforms, in ASIC
>> this is not needed
>> - The state machine is not an ideal solution as it creates a
>> kthread but it is needed because some sources might not be
>> very stable at sending the video (i.e. we must react
>> accordingly).
>>
>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>> Cc: Carlos Palminha <palminha@synopsys.com>
>> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
>> Cc: Hans Verkuil <hans.verkuil@cisco.com>
>> Cc: Sylwester Nawrocki <snawrocki@kernel.org>
>>
>> Changes from v4:
>> - Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
>> - Remove some comments and change some messages to dev_dbg
>> (Sylwester)
>> - Use v4l2_async_subnotifier_register() (Sylwester)
>> Changes from v3:
>> - Use v4l2 async API (Sylwester)
>> - Do not block waiting for phy
>> - Do not use busy waiting delays (Sylwester)
>> - Simplify dw_hdmi_power_on (Sylwester)
>> - Use clock API (Sylwester)
>> - Use compatible string (Sylwester)
>> - Minor fixes (Sylwester)
>> Changes from v2:
>> - Address review comments from Hans regarding CEC
>> - Use CEC notifier
>> - Enable SCDC
>> Changes from v1:
>> - Add support for CEC
>> - Correct typo errors
>> - Correctly detect interlaced video modes
>> - Correct VIC parsing
>> Changes from RFC:
>> - Add support for HDCP 1.4
>> - Fixup HDMI_VIC not being parsed (Hans)
>> - Send source change signal when powering off (Hans)
>> - Add a "wait stable delay"
>> - Detect interlaced video modes (Hans)
>> - Restrain g/s_register from reading/writing to HDCP regs
>> (Hans)
>> ---
>> drivers/media/platform/dwc/Kconfig | 15 +
>> drivers/media/platform/dwc/Makefile | 1 +
>> drivers/media/platform/dwc/dw-hdmi-rx.c | 1824
>> +++++++++++++++++++++++++++++++
>> drivers/media/platform/dwc/dw-hdmi-rx.h | 441 ++++++++
>> include/media/dwc/dw-hdmi-rx-pdata.h | 97 ++
>> 5 files changed, 2378 insertions(+)
>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>> create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
>>
>> diff --git a/drivers/media/platform/dwc/Kconfig
>> b/drivers/media/platform/dwc/Kconfig
>> index 361d38d..3ddccde 100644
>> --- a/drivers/media/platform/dwc/Kconfig
>> +++ b/drivers/media/platform/dwc/Kconfig
>> @@ -6,3 +6,18 @@ config VIDEO_DWC_HDMI_PHY_E405
>> To compile this driver as a module, choose M here.
>> The module
>> will be called dw-hdmi-phy-e405.
>> +
>> +config VIDEO_DWC_HDMI_RX
>> + tristate "Synopsys Designware HDMI Receiver driver"
>> + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
>> + help
>> + Support for Synopsys Designware HDMI RX controller.
>> +
>> + To compile this driver as a module, choose M here. The
>> module
>> + will be called dw-hdmi-rx.
>> +
>> +config VIDEO_DWC_HDMI_RX_CEC
>> + bool
>> + depends on VIDEO_DWC_HDMI_RX
>> + select CEC_CORE
>> + select CEC_NOTIFIER
>> diff --git a/drivers/media/platform/dwc/Makefile
>> b/drivers/media/platform/dwc/Makefile
>> index fc3b62c..cd04ca9 100644
>> --- a/drivers/media/platform/dwc/Makefile
>> +++ b/drivers/media/platform/dwc/Makefile
>> @@ -1 +1,2 @@
>> obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
>> +obj-$(CONFIG_VIDEO_DWC_HDMI_RX) += dw-hdmi-rx.o
>> diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.c
>> b/drivers/media/platform/dwc/dw-hdmi-rx.c
>> new file mode 100644
>> index 0000000..4a7b8fc
>> --- /dev/null
>> +++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
>
> <snip>
>
>> +static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev
>> *dw_dev, u32 stat)
>> +{
>> + if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) &
>> HDMI_CEC_CTRL_SEND_MASK) {
>> + dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
>> + return;
>> + }
>> +
>> + if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
>> + dev_dbg(dw_dev->dev, "%s: arbitration lost\n",
>> __func__);
>> + cec_transmit_done(dw_dev->cec_adap,
>> CEC_TX_STATUS_ARB_LOST,
>> + 1, 0, 0, 0);
>> + return;
>> + }
>> +
>> + if (stat & HDMI_AUD_CEC_ISTS_DONE) {
>> + dev_dbg(dw_dev->dev, "%s: transmission done\n",
>> __func__);
>> + cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_OK,
>> 0, 0, 0, 0);
>> + return;
>> + }
>> +
>> + if (stat & HDMI_AUD_CEC_ISTS_NACK) {
>> + dev_dbg(dw_dev->dev, "%s: got NACK\n", __func__);
>> + cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_NACK,
>> + 0, 1, 0, 0);
>> + return;
>> + }
>> +
>> + if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
>> + dev_dbg(dw_dev->dev, "%s: got initiator error\n",
>> __func__);
>> + cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_ERROR,
>> + 0, 0, 0, 1);
>> + return;
>> + }
>
> A few remarks/questions about this:
>
> 1) You can drop the dev_dbg for status ARB_LOST, NACK and OK
> since the cec
> core can log that as well.
Ok.
>
> 2) Shouldn't the ISTS_DONE test be done either at the beginning
> or at the
> end? If that bit is set, then can any of the other
> ARBLST/NACK/ERROR_INIT bits
> be set as well?
Never happened to me. But I will move it to the end, just to be
sure. BTW, I guess it can happen if the IRQ status is not cleared
when the IRQ fires.
>
> 3) Use the new cec_transmit_attempt_done() function instead of
> cec_transmit_done.
> It simplifies the code a little bit.
Ok.
>
>
>> +}
>> +
>> +static void dw_hdmi_cec_received_msg(struct dw_hdmi_dev *dw_dev)
>> +{
>> + struct cec_msg msg;
>> + u8 i;
>> +
>> + msg.len = hdmi_readl(dw_dev, HDMI_CEC_RX_CNT);
>> + if (!msg.len || msg.len > HDMI_CEC_RX_DATA_MAX)
>> + return; /* it's an invalid/non-existent message */
>> +
>> + for (i = 0; i < msg.len; i++)
>> + msg.msg[i] = hdmi_readl(dw_dev, HDMI_CEC_RX_DATA(i));
>> +
>> + hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
>> + cec_received_msg(dw_dev->cec_adap, &msg);
>> +}
>> +
>> +static int dw_hdmi_cec_adap_enable(struct cec_adapter *adap,
>> bool enable)
>> +{
>> + struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
>> +
>> + if (!dw_dev->cec_enabled_adap && enable) {
>> + hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
>> + hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
>> + hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
>> + dw_hdmi_cec_clear_ints(dw_dev);
>> + dw_hdmi_cec_enable_ints(dw_dev);
>> + } else if (dw_dev->cec_enabled_adap && !enable) {
>> + hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
>> + hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
>> + dw_hdmi_cec_disable_ints(dw_dev);
>> + dw_hdmi_cec_clear_ints(dw_dev);
>> + }
>> +
>> + dw_dev->cec_enabled_adap = enable;
>
> No need for this: this callback will only be called when the
> enable state
> really changes.
Ok.
<snip>
>> +static int dw_hdmi_log_status(struct v4l2_subdev *sd)
>> +{
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> + struct v4l2_dv_timings timings;
>> +
>> + v4l2_info(sd, "--- Chip configuration ---\n");
>> + v4l2_info(sd, "cfg_clk=%dMHz\n", dw_dev->cfg_clk);
>> + v4l2_info(sd, "phy_drv=%s, phy_jtag_addr=0x%x\n",
>> dw_dev->phy_drv,
>> + dw_dev->phy_jtag_addr);
>> +
>> + v4l2_info(sd, "--- Chip status ---\n");
>> + v4l2_info(sd, "selected_input=%d: signal=%d\n",
>> dw_dev->selected_input,
>> + has_signal(dw_dev, dw_dev->selected_input));
>> + v4l2_info(sd, "configured_input=%d: signal=%d\n",
>> + dw_dev->configured_input,
>> + has_signal(dw_dev, dw_dev->configured_input));
>> +
>> + v4l2_info(sd, "--- CEC status ---\n");
>> + v4l2_info(sd, "enabled=%s\n", dw_dev->cec_enabled_adap ?
>> "yes" : "no");
>> +
>> + v4l2_info(sd, "--- Video status ---\n");
>> + v4l2_info(sd, "type=%s, color_depth=%dbits",
>> + hdmi_readl(dw_dev, HDMI_PDEC_STS) &
>> + HDMI_PDEC_STS_DVIDET ? "dvi" : "hdmi",
>> + dw_hdmi_get_colordepth(dw_dev));
>> +
>> + v4l2_info(sd, "--- Video timings ---\n");
>> + if (dw_hdmi_query_dv_timings(sd, &timings))
>> + v4l2_info(sd, "No video detected\n");
>> + else
>> + v4l2_print_dv_timings(sd->name, "Detected format: ",
>> + &timings, true);
>> + v4l2_print_dv_timings(sd->name, "Configured format: ",
>> + &dw_dev->timings, true);
>
> Call v4l2_ctrl_subdev_log_status at the end.
Ok.
>
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_VIDEO_ADV_DEBUG
>> +static void dw_hdmi_invalid_register(struct dw_hdmi_dev
>> *dw_dev, u64 reg)
>> +{
>> + dev_err(dw_dev->dev, "register 0x%llx not supported\n",
>> reg);
>> + dev_err(dw_dev->dev, "0x0000-0x7fff: Main controller
>> map\n");
>> + dev_err(dw_dev->dev, "0x8000-0x80ff: PHY map\n");
>> +}
>> +
>> +static bool dw_hdmi_is_reserved_register(struct dw_hdmi_dev
>> *dw_dev, u32 reg)
>> +{
>> + if (reg >= HDMI_HDCP_CTRL && reg <= HDMI_HDCP_STS)
>> + return true;
>> + if (reg == HDMI_HDCP22_CONTROL)
>> + return true;
>> + if (reg == HDMI_HDCP22_STATUS)
>> + return true;
>> + return false;
>> +}
>> +
>> +static int dw_hdmi_g_register(struct v4l2_subdev *sd,
>> + struct v4l2_dbg_register *reg)
>> +{
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +
>> + switch (reg->reg >> 15) {
>> + case 0: /* Controller core read */
>> + if (dw_hdmi_is_reserved_register(dw_dev, reg->reg &
>> 0x7fff))
>> + return -EINVAL;
>
> Is this necessary? Obviously you shouldn't be able to set it,
> but I think it
> should be fine to read it. Up to you, though.
Actually some of the HDCP 1.4 registers are write only and if
someone tries to read the controller will not respond and will
block the bus. This is no problem for x86, but for some archs it
can block the system entirely.
>
>> +
>> + reg->size = 4;
>> + reg->val = hdmi_readl(dw_dev, reg->reg & 0x7fff);
>> + return 0;
>> + case 1: /* PHY read */
>> + if ((reg->reg & ~0xff) != BIT(15))
>> + break;
>> +
>> + reg->size = 2;
>> + reg->val = dw_hdmi_phy_read(dw_dev, reg->reg & 0xff);
>> + return 0;
>> + default:
>> + break;
>> + }
>> +
>> + dw_hdmi_invalid_register(dw_dev, reg->reg);
>> + return 0;
>> +}
>> +
>> +static int dw_hdmi_s_register(struct v4l2_subdev *sd,
>> + const struct v4l2_dbg_register *reg)
>> +{
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +
>> + switch (reg->reg >> 15) {
>> + case 0: /* Controller core write */
>> + if (dw_hdmi_is_reserved_register(dw_dev, reg->reg &
>> 0x7fff))
>> + return -EINVAL;
>> +
>> + hdmi_writel(dw_dev, reg->val & GENMASK(31,0),
>> reg->reg & 0x7fff);
>> + return 0;
>> + case 1: /* PHY write */
>> + if ((reg->reg & ~0xff) != BIT(15))
>> + break;
>> + dw_hdmi_phy_write(dw_dev, reg->val & 0xffff, reg->reg
>> & 0xff);
>> + return 0;
>> + default:
>> + break;
>> + }
>> +
>> + dw_hdmi_invalid_register(dw_dev, reg->reg);
>> + return 0;
>> +}
>> +#endif
>> +
>> +static int dw_hdmi_subscribe_event(struct v4l2_subdev *sd,
>> struct v4l2_fh *fh,
>> + struct v4l2_event_subscription *sub)
>> +{
>> + switch (sub->type) {
>> + case V4L2_EVENT_SOURCE_CHANGE:
>> + return v4l2_src_change_event_subdev_subscribe(sd, fh,
>> sub);
>> + default:
>> + return -EINVAL;
>
> Fall back to v4l2_ctrl_subdev_subscribe_event. See below for
> more info.
>
>> + }
>> +}
>> +
>> +static int dw_hdmi_registered(struct v4l2_subdev *sd)
>> +{
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> + int ret;
>> +
>> + ret = cec_register_adapter(dw_dev->cec_adap, dw_dev->dev);
>> + if (ret) {
>> + dev_err(dw_dev->dev, "failed to register CEC
>> adapter\n");
>> + cec_delete_adapter(dw_dev->cec_adap);
>> + return ret;
>> + }
>> +
>> + cec_register_cec_notifier(dw_dev->cec_adap,
>> dw_dev->cec_notifier);
>> + dw_dev->registered = true;
>> +
>> + return v4l2_async_subnotifier_register(&dw_dev->sd,
>> + &dw_dev->v4l2_notifier);
>> +}
>> +
>> +static void dw_hdmi_unregistered(struct v4l2_subdev *sd)
>> +{
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +
>> + cec_unregister_adapter(dw_dev->cec_adap);
>> + cec_notifier_put(dw_dev->cec_notifier);
>> + v4l2_async_subnotifier_unregister(&dw_dev->v4l2_notifier);
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops dw_hdmi_sd_core_ops = {
>> + .log_status = dw_hdmi_log_status,
>> +#ifdef CONFIG_VIDEO_ADV_DEBUG
>> + .g_register = dw_hdmi_g_register,
>> + .s_register = dw_hdmi_s_register,
>> +#endif
>> + .subscribe_event = dw_hdmi_subscribe_event,
>> +};
>> +
>> +static const struct v4l2_subdev_video_ops
>> dw_hdmi_sd_video_ops = {
>> + .s_routing = dw_hdmi_s_routing,
>> + .g_input_status = dw_hdmi_g_input_status,
>> + .g_parm = dw_hdmi_g_parm,
>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>
> No s_dv_timings???
Hmm, yeah, I didn't implement it because the callchain and the
player I use just use {get/set}_fmt. s_dv_timings can just
populate the fields and replace them with the detected dv_timings
? Just like set_fmt does? Because the controller has no scaler.
>
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops dw_hdmi_sd_pad_ops = {
>> + .enum_mbus_code = dw_hdmi_enum_mbus_code,
>> + .get_fmt = dw_hdmi_get_fmt,
>> + .set_fmt = dw_hdmi_set_fmt,
>> + .dv_timings_cap = dw_hdmi_dv_timings_cap,
>> + .enum_dv_timings = dw_hdmi_enum_dv_timings,
>> +};
>> +
>> +static const struct v4l2_subdev_ops dw_hdmi_sd_ops = {
>> + .core = &dw_hdmi_sd_core_ops,
>> + .video = &dw_hdmi_sd_video_ops,
>> + .pad = &dw_hdmi_sd_pad_ops,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops
>> dw_hdmi_internal_ops = {
>> + .registered = dw_hdmi_registered,
>> + .unregistered = dw_hdmi_unregistered,
>> +};
>> +
>> +static int dw_hdmi_v4l2_notify_bound(struct
>> v4l2_async_notifier *notifier,
>> + struct v4l2_subdev *subdev, struct v4l2_async_subdev
>> *asd)
>> +{
>> + struct dw_hdmi_dev *dw_dev = notifier_to_dw_dev(notifier);
>> +
>> + if (dw_dev->phy_async_sd.match.fwnode.fwnode ==
>> + of_fwnode_handle(subdev->dev->of_node)) {
>> + dev_dbg(dw_dev->dev, "found new subdev '%s'\n",
>> subdev->name);
>> + dw_dev->phy_sd = subdev;
>> + return 0;
>> + }
>> +
>> + return -EINVAL;
>> +}
>> +
>> +static void dw_hdmi_v4l2_notify_unbind(struct
>> v4l2_async_notifier *notifier,
>> + struct v4l2_subdev *subdev, struct v4l2_async_subdev
>> *asd)
>> +{
>> + struct dw_hdmi_dev *dw_dev = notifier_to_dw_dev(notifier);
>> +
>> + if (dw_dev->phy_sd == subdev) {
>> + dev_dbg(dw_dev->dev, "unbinding '%s'\n", subdev->name);
>> + dw_dev->phy_sd = NULL;
>> + }
>> +}
>> +
>> +static int dw_hdmi_v4l2_init_notifier(struct dw_hdmi_dev
>> *dw_dev)
>> +{
>> + struct v4l2_async_subdev **subdevs = NULL;
>> + struct device_node *child = NULL;
>> +
>> + subdevs = devm_kzalloc(dw_dev->dev, sizeof(*subdevs),
>> GFP_KERNEL);
>> + if (!subdevs)
>> + return -ENOMEM;
>> +
>> + child = dw_hdmi_get_phy_of_node(dw_dev, NULL);
>> + if (!child)
>> + return -EINVAL;
>> +
>> + dw_dev->phy_async_sd.match.fwnode.fwnode =
>> of_fwnode_handle(child);
>> + dw_dev->phy_async_sd.match_type = V4L2_ASYNC_MATCH_FWNODE;
>> +
>> + subdevs[0] = &dw_dev->phy_async_sd;
>> + dw_dev->v4l2_notifier.num_subdevs = 1;
>> + dw_dev->v4l2_notifier.subdevs = subdevs;
>> + dw_dev->v4l2_notifier.bound = dw_hdmi_v4l2_notify_bound;
>> + dw_dev->v4l2_notifier.unbind = dw_hdmi_v4l2_notify_unbind;
>> +
>> + return 0;
>> +}
>> +
>> +static int dw_hdmi_parse_dt(struct dw_hdmi_dev *dw_dev)
>> +{
>> + struct device_node *notifier, *phy_node, *np =
>> dw_dev->of_node;
>> + u32 tmp;
>> + int ret;
>> +
>> + if (!np) {
>> + dev_err(dw_dev->dev, "missing DT node\n");
>> + return -EINVAL;
>> + }
>> +
>> + /* PHY properties parsing */
>> + phy_node = dw_hdmi_get_phy_of_node(dw_dev, NULL);
>> + of_property_read_u32(phy_node, "reg", &tmp);
>> +
>> + dw_dev->phy_jtag_addr = tmp & 0xff;
>> + if (!dw_dev->phy_jtag_addr) {
>> + dev_err(dw_dev->dev, "missing phy jtag address in
>> DT\n");
>> + return -EINVAL;
>> + }
>> +
>> + /* Get config clock value */
>> + dw_dev->clk = devm_clk_get(dw_dev->dev, "cfg");
>> + if (IS_ERR(dw_dev->clk)) {
>> + dev_err(dw_dev->dev, "failed to get cfg clock\n");
>> + return PTR_ERR(dw_dev->clk);
>> + }
>> +
>> + ret = clk_prepare_enable(dw_dev->clk);
>> + if (ret) {
>> + dev_err(dw_dev->dev, "failed to enable cfg clock\n");
>> + return ret;
>> + }
>> +
>> + dw_dev->cfg_clk = clk_get_rate(dw_dev->clk) / 1000000U;
>> + if (!dw_dev->cfg_clk) {
>> + dev_err(dw_dev->dev, "invalid cfg clock frequency\n");
>> + ret = -EINVAL;
>> + goto err_clk;
>> + }
>> +
>> +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
>> + /* Notifier device parsing */
>> + notifier = of_parse_phandle(np, "edid-phandle", 0);
>> + if (!notifier) {
>> + dev_err(dw_dev->dev, "missing edid-phandle in DT\n");
>> + ret = -EINVAL;
>> + goto err_clk;
>> + }
>> +
>> + dw_dev->notifier_pdev = of_find_device_by_node(notifier);
>> + if (!dw_dev->notifier_pdev)
>> + return -EPROBE_DEFER;
>> +#endif
>> +
>> + return 0;
>> +
>> +err_clk:
>> + clk_disable_unprepare(dw_dev->clk);
>> + return ret;
>> +}
>> +
>> +static int dw_hdmi_rx_probe(struct platform_device *pdev)
>> +{
>> + const struct v4l2_dv_timings timings_def =
>> HDMI_DEFAULT_TIMING;
>> + struct dw_hdmi_rx_pdata *pdata = pdev->dev.platform_data;
>> + struct device *dev = &pdev->dev;
>> + struct dw_hdmi_dev *dw_dev;
>> + struct v4l2_subdev *sd;
>> + struct resource *res;
>> + int ret, irq;
>> +
>> + dev_dbg(dev, "%s\n", __func__);
>> +
>> + dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
>> + if (!dw_dev)
>> + return -ENOMEM;
>> +
>> + if (!pdata) {
>> + dev_err(dev, "missing platform data\n");
>> + return -EINVAL;
>> + }
>> +
>> + dw_dev->dev = dev;
>> + dw_dev->config = pdata;
>> + dw_dev->state = HDMI_STATE_NO_INIT;
>> + dw_dev->of_node = dev->of_node;
>> + spin_lock_init(&dw_dev->lock);
>> +
>> + ret = dw_hdmi_parse_dt(dw_dev);
>> + if (ret)
>> + return ret;
>> +
>> + /* Deferred work */
>> + dw_dev->wq =
>> create_singlethread_workqueue(DW_HDMI_RX_DRVNAME);
>> + if (!dw_dev->wq) {
>> + dev_err(dev, "failed to create workqueue\n");
>> + return -ENOMEM;
>> + }
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + dw_dev->regs = devm_ioremap_resource(dev, res);
>> + if (IS_ERR(dw_dev->regs)) {
>> + dev_err(dev, "failed to remap resource\n");
>> + ret = PTR_ERR(dw_dev->regs);
>> + goto err_wq;
>> + }
>> +
>> + /* Disable HPD as soon as posssible */
>> + dw_hdmi_disable_hpd(dw_dev);
>> +
>> + ret = dw_hdmi_config_hdcp(dw_dev);
>> + if (ret)
>> + goto err_wq;
>> +
>> + irq = platform_get_irq(pdev, 0);
>> + if (irq < 0) {
>> + ret = irq;
>> + goto err_wq;
>> + }
>> +
>> + ret = devm_request_threaded_irq(dev, irq, NULL,
>> dw_hdmi_irq_handler,
>> + IRQF_ONESHOT, DW_HDMI_RX_DRVNAME, dw_dev);
>> + if (ret)
>> + goto err_wq;
>> +
>> + irq = platform_get_irq(pdev, 1);
>> + if (irq < 0) {
>> + ret = irq;
>> + goto err_wq;
>> + }
>> +
>> + ret = devm_request_threaded_irq(dev, irq,
>> dw_hdmi_5v_hard_irq_handler,
>> + dw_hdmi_5v_irq_handler, IRQF_ONESHOT,
>> + DW_HDMI_RX_DRVNAME "-5v-handler", dw_dev);
>> + if (ret)
>> + goto err_wq;
>> +
>> + /* V4L2 initialization */
>> + sd = &dw_dev->sd;
>> + v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
>> + strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
>> + sd->dev = dev;
>> + sd->internal_ops = &dw_hdmi_internal_ops;
>> + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS |
>> V4L2_SUBDEV_FL_HAS_DEVNODE;
>
> You need to add at this control: V4L2_CID_DV_RX_POWER_PRESENT.
> This is a
> read-only control that reports the 5V status. Important for
> applications to have.
Ok.
>
> I gather that this IP doesn't handle InfoFrames? If it does,
> then let me know.
Yes, it handles but I didn't implement the parsing yet (I just
parse the VIC for now).
>
>> +
>> + /* Notifier for subdev binding */
>> + ret = dw_hdmi_v4l2_init_notifier(dw_dev);
>> + if (ret) {
>> + dev_err(dev, "failed to init v4l2 notifier\n");
>> + goto err_wq;
>> + }
>> +
>> + /* PHY loading */
>> + ret = dw_hdmi_phy_init(dw_dev);
>> + if (ret)
>> + goto err_wq;
>> +
>> + /* CEC */
>> +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
>> + dw_dev->cec_adap =
>> cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
>> + dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
>> + CEC_CAP_LOG_ADDRS | CEC_CAP_RC |
>> CEC_CAP_PASSTHROUGH,
>> + HDMI_CEC_MAX_LOG_ADDRS);
>> + ret = PTR_ERR_OR_ZERO(dw_dev->cec_adap);
>> + if (ret) {
>> + dev_err(dev, "failed to allocate CEC adapter\n");
>> + goto err_phy;
>> + }
>> +
>> + dw_dev->cec_notifier =
>> cec_notifier_get(&dw_dev->notifier_pdev->dev);
>> + if (!dw_dev->cec_notifier) {
>> + dev_err(dev, "failed to allocate CEC notifier\n");
>> + ret = -ENOMEM;
>> + goto err_cec;
>> + }
>> +
>> + dev_info(dev, "CEC is enabled\n");
>> +#else
>> + dev_info(dev, "CEC is disabled\n");
>> +#endif
>> +
>> + ret = v4l2_async_register_subdev(sd);
>> + if (ret) {
>> + dev_err(dev, "failed to register subdev\n");
>> + goto err_cec;
>> + }
>> +
>> + /* Fill initial format settings */
>> + dw_dev->timings = timings_def;
>
> Unless I missed something it appears dw_dev->timings never
> changes value since this
> appears to be the only assignment. I'm fairly certain you need
> a s_dv_timings op as
> well.
>
>> + dw_dev->mbus_code = MEDIA_BUS_FMT_BGR888_1X24;
>> +
>> + dev_set_drvdata(dev, sd);
>> + dw_dev->state = HDMI_STATE_POWER_OFF;
>> + dw_hdmi_detect_tx_5v(dw_dev);
>> + dev_dbg(dev, "driver probed\n");
>> + return 0;
>> +
>> +err_cec:
>> + cec_delete_adapter(dw_dev->cec_adap);
>> +err_phy:
>> + dw_hdmi_phy_exit(dw_dev);
>> +err_wq:
>> + destroy_workqueue(dw_dev->wq);
>> + return ret;
>> +}
>> +
>> +static int dw_hdmi_rx_remove(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +
>> + dw_hdmi_disable_ints(dw_dev);
>> + dw_hdmi_disable_hpd(dw_dev);
>> + dw_hdmi_disable_scdc(dw_dev);
>> + dw_hdmi_power_off(dw_dev);
>> + dw_hdmi_phy_s_power(dw_dev, false);
>> + flush_workqueue(dw_dev->wq);
>> + destroy_workqueue(dw_dev->wq);
>> + dw_hdmi_phy_exit(dw_dev);
>> + v4l2_async_unregister_subdev(sd);
>> + clk_disable_unprepare(dw_dev->clk);
>> + dev_dbg(dev, "driver removed\n");
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id dw_hdmi_rx_id[] = {
>> + { .compatible = "snps,dw-hdmi-rx" },
>> + { },
>> +};
>> +MODULE_DEVICE_TABLE(of, dw_hdmi_rx_id);
>> +
>> +static struct platform_driver dw_hdmi_rx_driver = {
>> + .probe = dw_hdmi_rx_probe,
>> + .remove = dw_hdmi_rx_remove,
>> + .driver = {
>> + .name = DW_HDMI_RX_DRVNAME,
>> + .of_match_table = dw_hdmi_rx_id,
>> + }
>> +};
>> +module_platform_driver(dw_hdmi_rx_driver);
>>
<snip>
>> diff --git a/include/media/dwc/dw-hdmi-rx-pdata.h
>> b/include/media/dwc/dw-hdmi-rx-pdata.h
>> new file mode 100644
>> index 0000000..38c6d91
>> --- /dev/null
>> +++ b/include/media/dwc/dw-hdmi-rx-pdata.h
>> @@ -0,0 +1,97 @@
>> +/*
>> + * Synopsys Designware HDMI Receiver controller platform data
>> + *
>> + * This Synopsys dw-hdmi-rx software and associated
>> documentation
>> + * (hereinafter the "Software") is an unsupported proprietary
>> work of
>> + * Synopsys, Inc. unless otherwise expressly agreed to in
>> writing between
>> + * Synopsys and you. The Software IS NOT an item of Licensed
>> Software or a
>> + * Licensed Product under any End User Software License
>> Agreement or
>> + * Agreement for Licensed Products with Synopsys or any
>> supplement thereto.
>> + * Synopsys is a registered trademark of Synopsys, Inc. Other
>> names included
>> + * in the SOFTWARE may be the trademarks of their respective
>> owners.
>> + *
>> + * The contents of this file are dual-licensed; you may
>> select either version 2
>> + * of the GNU General Public License (“GPL”) or the MIT
>> license (“MIT”).
>> + *
>> + * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
>> + *
>> + * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
>> KIND, EXPRESS OR
>> + * IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
>> MERCHANTABILITY,
>> + * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN
>> NO EVENT SHALL THE
>> + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
>> DAMAGES OR
>> + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT,
>> OR OTHERWISE
>> + * ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
>> THE USE OR
>> + * OTHER DEALINGS IN THE SOFTWARE.
>> + */
>> +
>> +#ifndef __DW_HDMI_RX_PDATA_H__
>> +#define __DW_HDMI_RX_PDATA_H__
>> +
>> +#define DW_HDMI_RX_DRVNAME "dw-hdmi-rx"
>> +
>> +/* Notify events */
>> +#define DW_HDMI_NOTIFY_IS_OFF 1
>> +#define DW_HDMI_NOTIFY_INPUT_CHANGED 2
>> +#define DW_HDMI_NOTIFY_AUDIO_CHANGED 3
>> +#define DW_HDMI_NOTIFY_IS_STABLE 4
>> +
>> +/* HDCP 1.4 */
>> +#define DW_HDMI_HDCP14_BKSV_SIZE 2
>> +#define DW_HDMI_HDCP14_KEYS_SIZE (2 * 40)
>> +
>> +/**
>> + * struct dw_hdmi_hdcp14_key - HDCP 1.4 keys structure.
>> + *
>> + * @seed: Seeed value for HDCP 1.4 engine (16 bits).
>
> Typo: Seeed -> Seed
>
>> + *
>> + * @bksv: BKSV value for HDCP 1.4 engine (40 bits).
>> + *
>> + * @keys: Keys value for HDCP 1.4 engine (80 * 56 bits).
>> + *
>> + * @keys_valid: Must be set to true if the keys in this
>> structure are valid
>> + * and can be used by the HDMI receiver controller.
>> + */
>> +struct dw_hdmi_hdcp14_key {
>> + u32 seed;
>> + u32 bksv[DW_HDMI_HDCP14_BKSV_SIZE];
>> + u32 keys[DW_HDMI_HDCP14_KEYS_SIZE];
>> + bool keys_valid;
>> +};
>> +
>> +/**
>> + * struct dw_hdmi_rx_pdata - Platform Data configuration for
>> HDMI receiver.
>> + *
>> + * @hdcp14_keys: Keys for HDCP 1.4 engine. See
>> @dw_hdmi_hdcp14_key.
>
> Was this for debugging only? These are the Device Private Keys
> you're talking about?
>
> If this is indeed the case, then this doesn't belong here. You
> should never rely on
> software to set these keys. It should be fused in the hardware,
> or read from an
> encrypted eeprom or something like that. None of this
> (including the bksv) should
> be settable from the driver. You can read the bksv since that's
> public.
>
> This can't be in a kernel driver, nor can it be set or read
> through the s_register API.
>
> Instead there should be a big fat disclaimer that how you
> program these keys is up to
> the hardware designer and that it should be in accordance to
> the HDCP requirements.
>
> I would drop this completely from the pdata. My recommendation
> would be to not include
> HDCP support at all for this first version. Add it in follow-up
> patches which include
> a new V4L2 API for handling HDCP. This needs to be handled
> carefully.
Yes, in real HW these keys will not be handled this way. I'm
using a prototyping system so its easier to debug. I will remove
this entirely and drop HDCP 1.4 support for now.
Hmm, I'm seeing the configuration flow for keys written in HW and
it actually just needs a seed (for encrypted keys, for decrypted
ones it just doesn't need anything). Shall I drop the support or
change the code? I've no way to test this right now though...
Best regards,
Jose Miguel Abreu
>
>> + *
>> + * @dw_5v_status: 5v status callback. Shall return the status
>> of the given
>> + * input, i.e. shall be true if a cable is connected to the
>> specified input.
>> + *
>> + * @dw_5v_clear: 5v clear callback. Shall clear the interrupt
>> associated with
>> + * the 5v sense controller.
>> + *
>> + * @dw_5v_arg: Argument to be used with the 5v sense callbacks.
>> + *
>> + * @dw_zcal_reset: Impedance calibration reset callback.
>> Shall be called when
>> + * the impedance calibration needs to be restarted. This is
>> used by phy driver
>> + * only.
>> + *
>> + * @dw_zcal_done: Impendace calibration status callback.
>> Shall return true if
>
> Typo: Impendace -> Impedance
>
>> + * the impedance calibration procedure has ended. This is
>> used by phy driver
>> + * only.
>> + *
>> + * @dw_zcal_arg: Argument to be used with the ZCAL
>> calibration callbacks.
>> + */
>> +struct dw_hdmi_rx_pdata {
>> + /* Controller configuration */
>> + struct dw_hdmi_hdcp14_key hdcp14_keys;
>> + /* 5V sense interface */
>> + bool (*dw_5v_status)(void __iomem *regs, int input);
>> + void (*dw_5v_clear)(void __iomem *regs);
>> + void __iomem *dw_5v_arg;
>> + /* Zcal interface */
>> + void (*dw_zcal_reset)(void __iomem *regs);
>> + bool (*dw_zcal_done)(void __iomem *regs);
>> + void __iomem *dw_zcal_arg;
>> +};
>> +
>> +#endif /* __DW_HDMI_RX_PDATA_H__ */
>>
>
> Regards,
>
> Hans
[toc] | [prev] | [next] | [standalone]
| From | Hans Verkuil <hverkuil@xs4all.nl> |
|---|---|
| Date | 2017-07-03 12:40 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZ6VP-5S-1@gated-at.bofh.it> |
| In reply to | #1679852 |
On 07/03/2017 11:53 AM, Jose Abreu wrote:
> Hi Hans,
>
>
> On 03-07-2017 10:27, Hans Verkuil wrote:
>> On 06/29/2017 12:46 PM, Jose Abreu wrote:
>>> This is an initial submission for the Synopsys Designware HDMI RX
>>> Controller Driver. This driver interacts with a phy driver so
>>> that
>>> a communication between them is created and a video pipeline is
>>> configured.
>>>
>>> The controller + phy pipeline can then be integrated into a fully
>>> featured system that can be able to receive video up to 4k@60Hz
>>> with deep color 48bit RGB, depending on the platform. Although,
>>> this initial version does not yet handle deep color modes.
>>>
>>> This driver was implemented as a standard V4L2 subdevice and its
>>> main features are:
>>> - Internal state machine that reconfigures phy until the
>>> video is not stable
>>> - JTAG communication with phy
>>> - Inter-module communication with phy driver
>>> - Debug write/read ioctls
>>>
>>> Some notes:
>>> - RX sense controller (cable connection/disconnection) must
>>> be handled by the platform wrapper as this is not integrated
>>> into the controller RTL
>>> - The same goes for EDID ROM's
>>> - ZCAL calibration is needed only in FPGA platforms, in ASIC
>>> this is not needed
>>> - The state machine is not an ideal solution as it creates a
>>> kthread but it is needed because some sources might not be
>>> very stable at sending the video (i.e. we must react
>>> accordingly).
>>>
>>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>>> Cc: Carlos Palminha <palminha@synopsys.com>
>>> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
>>> Cc: Hans Verkuil <hans.verkuil@cisco.com>
>>> Cc: Sylwester Nawrocki <snawrocki@kernel.org>
>>>
>>> Changes from v4:
>>> - Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
>>> - Remove some comments and change some messages to dev_dbg
>>> (Sylwester)
>>> - Use v4l2_async_subnotifier_register() (Sylwester)
>>> Changes from v3:
>>> - Use v4l2 async API (Sylwester)
>>> - Do not block waiting for phy
>>> - Do not use busy waiting delays (Sylwester)
>>> - Simplify dw_hdmi_power_on (Sylwester)
>>> - Use clock API (Sylwester)
>>> - Use compatible string (Sylwester)
>>> - Minor fixes (Sylwester)
>>> Changes from v2:
>>> - Address review comments from Hans regarding CEC
>>> - Use CEC notifier
>>> - Enable SCDC
>>> Changes from v1:
>>> - Add support for CEC
>>> - Correct typo errors
>>> - Correctly detect interlaced video modes
>>> - Correct VIC parsing
>>> Changes from RFC:
>>> - Add support for HDCP 1.4
>>> - Fixup HDMI_VIC not being parsed (Hans)
>>> - Send source change signal when powering off (Hans)
>>> - Add a "wait stable delay"
>>> - Detect interlaced video modes (Hans)
>>> - Restrain g/s_register from reading/writing to HDCP regs
>>> (Hans)
>>> ---
>>> drivers/media/platform/dwc/Kconfig | 15 +
>>> drivers/media/platform/dwc/Makefile | 1 +
>>> drivers/media/platform/dwc/dw-hdmi-rx.c | 1824
>>> +++++++++++++++++++++++++++++++
>>> drivers/media/platform/dwc/dw-hdmi-rx.h | 441 ++++++++
>>> include/media/dwc/dw-hdmi-rx-pdata.h | 97 ++
>>> 5 files changed, 2378 insertions(+)
>>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>>> create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
>>>
>>> diff --git a/drivers/media/platform/dwc/Kconfig
>>> b/drivers/media/platform/dwc/Kconfig
>>> index 361d38d..3ddccde 100644
>>> --- a/drivers/media/platform/dwc/Kconfig
>>> +++ b/drivers/media/platform/dwc/Kconfig
>>> @@ -6,3 +6,18 @@ config VIDEO_DWC_HDMI_PHY_E405
>>> To compile this driver as a module, choose M here.
>>> The module
>>> will be called dw-hdmi-phy-e405.
>>> +
>>> +config VIDEO_DWC_HDMI_RX
>>> + tristate "Synopsys Designware HDMI Receiver driver"
>>> + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
>>> + help
>>> + Support for Synopsys Designware HDMI RX controller.
>>> +
>>> + To compile this driver as a module, choose M here. The
>>> module
>>> + will be called dw-hdmi-rx.
>>> +
>>> +config VIDEO_DWC_HDMI_RX_CEC
>>> + bool
>>> + depends on VIDEO_DWC_HDMI_RX
>>> + select CEC_CORE
>>> + select CEC_NOTIFIER
>>> diff --git a/drivers/media/platform/dwc/Makefile
>>> b/drivers/media/platform/dwc/Makefile
>>> index fc3b62c..cd04ca9 100644
>>> --- a/drivers/media/platform/dwc/Makefile
>>> +++ b/drivers/media/platform/dwc/Makefile
>>> @@ -1 +1,2 @@
>>> obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
>>> +obj-$(CONFIG_VIDEO_DWC_HDMI_RX) += dw-hdmi-rx.o
>>> diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.c
>>> b/drivers/media/platform/dwc/dw-hdmi-rx.c
>>> new file mode 100644
>>> index 0000000..4a7b8fc
>>> --- /dev/null
>>> +++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
>>
>> <snip>
>>> +static int dw_hdmi_g_register(struct v4l2_subdev *sd,
>>> + struct v4l2_dbg_register *reg)
>>> +{
>>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>>> +
>>> + switch (reg->reg >> 15) {
>>> + case 0: /* Controller core read */
>>> + if (dw_hdmi_is_reserved_register(dw_dev, reg->reg &
>>> 0x7fff))
>>> + return -EINVAL;
>>
>> Is this necessary? Obviously you shouldn't be able to set it,
>> but I think it
>> should be fine to read it. Up to you, though.
>
> Actually some of the HDCP 1.4 registers are write only and if
> someone tries to read the controller will not respond and will
> block the bus. This is no problem for x86, but for some archs it
> can block the system entirely.
Worth a comment in that case.
>>> +static const struct v4l2_subdev_video_ops
>>> dw_hdmi_sd_video_ops = {
>>> + .s_routing = dw_hdmi_s_routing,
>>> + .g_input_status = dw_hdmi_g_input_status,
>>> + .g_parm = dw_hdmi_g_parm,
>>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>>
>> No s_dv_timings???
>
> Hmm, yeah, I didn't implement it because the callchain and the
> player I use just use {get/set}_fmt. s_dv_timings can just
> populate the fields and replace them with the detected dv_timings
> ? Just like set_fmt does? Because the controller has no scaler.
No, s_dv_timings is the function that actually sets dw_dev->timings.
After you check that it is valid of course (call v4l2_valid_dv_timings).
set_fmt calls get_fmt which returns the information from dw_dev->timings.
But it is s_dv_timings that has to set dw_dev->timings.
With the current code you can only capture 640x480 (the default timings).
Have you ever tested this with any other timings? I don't quite understand
how you test.
>
>>
>>> +};
>>> +
>>> +static const struct v4l2_subdev_pad_ops dw_hdmi_sd_pad_ops = {
>>> + .enum_mbus_code = dw_hdmi_enum_mbus_code,
>>> + .get_fmt = dw_hdmi_get_fmt,
>>> + .set_fmt = dw_hdmi_set_fmt,
>>> + .dv_timings_cap = dw_hdmi_dv_timings_cap,
>>> + .enum_dv_timings = dw_hdmi_enum_dv_timings,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops dw_hdmi_sd_ops = {
>>> + .core = &dw_hdmi_sd_core_ops,
>>> + .video = &dw_hdmi_sd_video_ops,
>>> + .pad = &dw_hdmi_sd_pad_ops,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_internal_ops
>>> dw_hdmi_internal_ops = {
>>> + .registered = dw_hdmi_registered,
>>> + .unregistered = dw_hdmi_unregistered,
>>> +};
>>> +
>>> +static int dw_hdmi_v4l2_notify_bound(struct
>>> v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *subdev, struct v4l2_async_subdev
>>> *asd)
>>> +{
>>> + struct dw_hdmi_dev *dw_dev = notifier_to_dw_dev(notifier);
>>> +
>>> + if (dw_dev->phy_async_sd.match.fwnode.fwnode ==
>>> + of_fwnode_handle(subdev->dev->of_node)) {
>>> + dev_dbg(dw_dev->dev, "found new subdev '%s'\n",
>>> subdev->name);
>>> + dw_dev->phy_sd = subdev;
>>> + return 0;
>>> + }
>>> +
>>> + return -EINVAL;
>>> +}
>>> +
>>> +static void dw_hdmi_v4l2_notify_unbind(struct
>>> v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *subdev, struct v4l2_async_subdev
>>> *asd)
>>> +{
>>> + struct dw_hdmi_dev *dw_dev = notifier_to_dw_dev(notifier);
>>> +
>>> + if (dw_dev->phy_sd == subdev) {
>>> + dev_dbg(dw_dev->dev, "unbinding '%s'\n", subdev->name);
>>> + dw_dev->phy_sd = NULL;
>>> + }
>>> +}
>>> +
>>> +static int dw_hdmi_v4l2_init_notifier(struct dw_hdmi_dev
>>> *dw_dev)
>>> +{
>>> + struct v4l2_async_subdev **subdevs = NULL;
>>> + struct device_node *child = NULL;
>>> +
>>> + subdevs = devm_kzalloc(dw_dev->dev, sizeof(*subdevs),
>>> GFP_KERNEL);
>>> + if (!subdevs)
>>> + return -ENOMEM;
>>> +
>>> + child = dw_hdmi_get_phy_of_node(dw_dev, NULL);
>>> + if (!child)
>>> + return -EINVAL;
>>> +
>>> + dw_dev->phy_async_sd.match.fwnode.fwnode =
>>> of_fwnode_handle(child);
>>> + dw_dev->phy_async_sd.match_type = V4L2_ASYNC_MATCH_FWNODE;
>>> +
>>> + subdevs[0] = &dw_dev->phy_async_sd;
>>> + dw_dev->v4l2_notifier.num_subdevs = 1;
>>> + dw_dev->v4l2_notifier.subdevs = subdevs;
>>> + dw_dev->v4l2_notifier.bound = dw_hdmi_v4l2_notify_bound;
>>> + dw_dev->v4l2_notifier.unbind = dw_hdmi_v4l2_notify_unbind;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int dw_hdmi_parse_dt(struct dw_hdmi_dev *dw_dev)
>>> +{
>>> + struct device_node *notifier, *phy_node, *np =
>>> dw_dev->of_node;
>>> + u32 tmp;
>>> + int ret;
>>> +
>>> + if (!np) {
>>> + dev_err(dw_dev->dev, "missing DT node\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + /* PHY properties parsing */
>>> + phy_node = dw_hdmi_get_phy_of_node(dw_dev, NULL);
>>> + of_property_read_u32(phy_node, "reg", &tmp);
>>> +
>>> + dw_dev->phy_jtag_addr = tmp & 0xff;
>>> + if (!dw_dev->phy_jtag_addr) {
>>> + dev_err(dw_dev->dev, "missing phy jtag address in
>>> DT\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + /* Get config clock value */
>>> + dw_dev->clk = devm_clk_get(dw_dev->dev, "cfg");
>>> + if (IS_ERR(dw_dev->clk)) {
>>> + dev_err(dw_dev->dev, "failed to get cfg clock\n");
>>> + return PTR_ERR(dw_dev->clk);
>>> + }
>>> +
>>> + ret = clk_prepare_enable(dw_dev->clk);
>>> + if (ret) {
>>> + dev_err(dw_dev->dev, "failed to enable cfg clock\n");
>>> + return ret;
>>> + }
>>> +
>>> + dw_dev->cfg_clk = clk_get_rate(dw_dev->clk) / 1000000U;
>>> + if (!dw_dev->cfg_clk) {
>>> + dev_err(dw_dev->dev, "invalid cfg clock frequency\n");
>>> + ret = -EINVAL;
>>> + goto err_clk;
>>> + }
>>> +
>>> +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
>>> + /* Notifier device parsing */
>>> + notifier = of_parse_phandle(np, "edid-phandle", 0);
>>> + if (!notifier) {
>>> + dev_err(dw_dev->dev, "missing edid-phandle in DT\n");
>>> + ret = -EINVAL;
>>> + goto err_clk;
>>> + }
>>> +
>>> + dw_dev->notifier_pdev = of_find_device_by_node(notifier);
>>> + if (!dw_dev->notifier_pdev)
>>> + return -EPROBE_DEFER;
>>> +#endif
>>> +
>>> + return 0;
>>> +
>>> +err_clk:
>>> + clk_disable_unprepare(dw_dev->clk);
>>> + return ret;
>>> +}
>>> +
>>> +static int dw_hdmi_rx_probe(struct platform_device *pdev)
>>> +{
>>> + const struct v4l2_dv_timings timings_def =
>>> HDMI_DEFAULT_TIMING;
>>> + struct dw_hdmi_rx_pdata *pdata = pdev->dev.platform_data;
>>> + struct device *dev = &pdev->dev;
>>> + struct dw_hdmi_dev *dw_dev;
>>> + struct v4l2_subdev *sd;
>>> + struct resource *res;
>>> + int ret, irq;
>>> +
>>> + dev_dbg(dev, "%s\n", __func__);
>>> +
>>> + dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
>>> + if (!dw_dev)
>>> + return -ENOMEM;
>>> +
>>> + if (!pdata) {
>>> + dev_err(dev, "missing platform data\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + dw_dev->dev = dev;
>>> + dw_dev->config = pdata;
>>> + dw_dev->state = HDMI_STATE_NO_INIT;
>>> + dw_dev->of_node = dev->of_node;
>>> + spin_lock_init(&dw_dev->lock);
>>> +
>>> + ret = dw_hdmi_parse_dt(dw_dev);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /* Deferred work */
>>> + dw_dev->wq =
>>> create_singlethread_workqueue(DW_HDMI_RX_DRVNAME);
>>> + if (!dw_dev->wq) {
>>> + dev_err(dev, "failed to create workqueue\n");
>>> + return -ENOMEM;
>>> + }
>>> +
>>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> + dw_dev->regs = devm_ioremap_resource(dev, res);
>>> + if (IS_ERR(dw_dev->regs)) {
>>> + dev_err(dev, "failed to remap resource\n");
>>> + ret = PTR_ERR(dw_dev->regs);
>>> + goto err_wq;
>>> + }
>>> +
>>> + /* Disable HPD as soon as posssible */
>>> + dw_hdmi_disable_hpd(dw_dev);
>>> +
>>> + ret = dw_hdmi_config_hdcp(dw_dev);
>>> + if (ret)
>>> + goto err_wq;
>>> +
>>> + irq = platform_get_irq(pdev, 0);
>>> + if (irq < 0) {
>>> + ret = irq;
>>> + goto err_wq;
>>> + }
>>> +
>>> + ret = devm_request_threaded_irq(dev, irq, NULL,
>>> dw_hdmi_irq_handler,
>>> + IRQF_ONESHOT, DW_HDMI_RX_DRVNAME, dw_dev);
>>> + if (ret)
>>> + goto err_wq;
>>> +
>>> + irq = platform_get_irq(pdev, 1);
>>> + if (irq < 0) {
>>> + ret = irq;
>>> + goto err_wq;
>>> + }
>>> +
>>> + ret = devm_request_threaded_irq(dev, irq,
>>> dw_hdmi_5v_hard_irq_handler,
>>> + dw_hdmi_5v_irq_handler, IRQF_ONESHOT,
>>> + DW_HDMI_RX_DRVNAME "-5v-handler", dw_dev);
>>> + if (ret)
>>> + goto err_wq;
>>> +
>>> + /* V4L2 initialization */
>>> + sd = &dw_dev->sd;
>>> + v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
>>> + strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
>>> + sd->dev = dev;
>>> + sd->internal_ops = &dw_hdmi_internal_ops;
>>> + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS |
>>> V4L2_SUBDEV_FL_HAS_DEVNODE;
>>
>> You need to add at this control: V4L2_CID_DV_RX_POWER_PRESENT.
>> This is a
>> read-only control that reports the 5V status. Important for
>> applications to have.
>
> Ok.
>
>>
>> I gather that this IP doesn't handle InfoFrames? If it does,
>> then let me know.
>
> Yes, it handles but I didn't implement the parsing yet (I just
> parse the VIC for now).
Ah, OK. When you add that, then I strongly recommend that you also add
support for the V4L2_CID_DV_RX_RGB_RANGE control, provided this IP can
do quantization range conversion. If quantization range conversion is not
part of this IP, then just ignore this comment.
>
>>
>>> +
>>> + /* Notifier for subdev binding */
>>> + ret = dw_hdmi_v4l2_init_notifier(dw_dev);
>>> + if (ret) {
>>> + dev_err(dev, "failed to init v4l2 notifier\n");
>>> + goto err_wq;
>>> + }
>>> +
>>> + /* PHY loading */
>>> + ret = dw_hdmi_phy_init(dw_dev);
>>> + if (ret)
>>> + goto err_wq;
>>> +
>>> + /* CEC */
>>> +#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
>>> + dw_dev->cec_adap =
>>> cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
>>> + dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
>>> + CEC_CAP_LOG_ADDRS | CEC_CAP_RC |
>>> CEC_CAP_PASSTHROUGH,
>>> + HDMI_CEC_MAX_LOG_ADDRS);
>>> + ret = PTR_ERR_OR_ZERO(dw_dev->cec_adap);
>>> + if (ret) {
>>> + dev_err(dev, "failed to allocate CEC adapter\n");
>>> + goto err_phy;
>>> + }
>>> +
>>> + dw_dev->cec_notifier =
>>> cec_notifier_get(&dw_dev->notifier_pdev->dev);
>>> + if (!dw_dev->cec_notifier) {
>>> + dev_err(dev, "failed to allocate CEC notifier\n");
>>> + ret = -ENOMEM;
>>> + goto err_cec;
>>> + }
>>> +
>>> + dev_info(dev, "CEC is enabled\n");
>>> +#else
>>> + dev_info(dev, "CEC is disabled\n");
>>> +#endif
>>> +
>>> + ret = v4l2_async_register_subdev(sd);
>>> + if (ret) {
>>> + dev_err(dev, "failed to register subdev\n");
>>> + goto err_cec;
>>> + }
>>> +
>>> + /* Fill initial format settings */
>>> + dw_dev->timings = timings_def;
>>
>> Unless I missed something it appears dw_dev->timings never
>> changes value since this
>> appears to be the only assignment. I'm fairly certain you need
>> a s_dv_timings op as
>> well.
>>
>>> + dw_dev->mbus_code = MEDIA_BUS_FMT_BGR888_1X24;
>>> +
>>> + dev_set_drvdata(dev, sd);
>>> + dw_dev->state = HDMI_STATE_POWER_OFF;
>>> + dw_hdmi_detect_tx_5v(dw_dev);
>>> + dev_dbg(dev, "driver probed\n");
>>> + return 0;
>>> +
>>> +err_cec:
>>> + cec_delete_adapter(dw_dev->cec_adap);
>>> +err_phy:
>>> + dw_hdmi_phy_exit(dw_dev);
>>> +err_wq:
>>> + destroy_workqueue(dw_dev->wq);
>>> + return ret;
>>> +}
>>> +
>>> +static int dw_hdmi_rx_remove(struct platform_device *pdev)
>>> +{
>>> + struct device *dev = &pdev->dev;
>>> + struct v4l2_subdev *sd = dev_get_drvdata(dev);
>>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>>> +
>>> + dw_hdmi_disable_ints(dw_dev);
>>> + dw_hdmi_disable_hpd(dw_dev);
>>> + dw_hdmi_disable_scdc(dw_dev);
>>> + dw_hdmi_power_off(dw_dev);
>>> + dw_hdmi_phy_s_power(dw_dev, false);
>>> + flush_workqueue(dw_dev->wq);
>>> + destroy_workqueue(dw_dev->wq);
>>> + dw_hdmi_phy_exit(dw_dev);
>>> + v4l2_async_unregister_subdev(sd);
>>> + clk_disable_unprepare(dw_dev->clk);
>>> + dev_dbg(dev, "driver removed\n");
>>> + return 0;
>>> +}
>>> +
>>> +static const struct of_device_id dw_hdmi_rx_id[] = {
>>> + { .compatible = "snps,dw-hdmi-rx" },
>>> + { },
>>> +};
>>> +MODULE_DEVICE_TABLE(of, dw_hdmi_rx_id);
>>> +
>>> +static struct platform_driver dw_hdmi_rx_driver = {
>>> + .probe = dw_hdmi_rx_probe,
>>> + .remove = dw_hdmi_rx_remove,
>>> + .driver = {
>>> + .name = DW_HDMI_RX_DRVNAME,
>>> + .of_match_table = dw_hdmi_rx_id,
>>> + }
>>> +};
>>> +module_platform_driver(dw_hdmi_rx_driver);
>>>
>
> <snip>
>
>>> diff --git a/include/media/dwc/dw-hdmi-rx-pdata.h
>>> b/include/media/dwc/dw-hdmi-rx-pdata.h
>>> new file mode 100644
>>> index 0000000..38c6d91
>>> --- /dev/null
>>> +++ b/include/media/dwc/dw-hdmi-rx-pdata.h
>>> @@ -0,0 +1,97 @@
>>> +/*
>>> + * Synopsys Designware HDMI Receiver controller platform data
>>> + *
>>> + * This Synopsys dw-hdmi-rx software and associated
>>> documentation
>>> + * (hereinafter the "Software") is an unsupported proprietary
>>> work of
>>> + * Synopsys, Inc. unless otherwise expressly agreed to in
>>> writing between
>>> + * Synopsys and you. The Software IS NOT an item of Licensed
>>> Software or a
>>> + * Licensed Product under any End User Software License
>>> Agreement or
>>> + * Agreement for Licensed Products with Synopsys or any
>>> supplement thereto.
>>> + * Synopsys is a registered trademark of Synopsys, Inc. Other
>>> names included
>>> + * in the SOFTWARE may be the trademarks of their respective
>>> owners.
>>> + *
>>> + * The contents of this file are dual-licensed; you may
>>> select either version 2
>>> + * of the GNU General Public License (“GPL”) or the MIT
>>> license (“MIT”).
>>> + *
>>> + * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
>>> + *
>>> + * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
>>> KIND, EXPRESS OR
>>> + * IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
>>> MERCHANTABILITY,
>>> + * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN
>>> NO EVENT SHALL THE
>>> + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
>>> DAMAGES OR
>>> + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT,
>>> OR OTHERWISE
>>> + * ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
>>> THE USE OR
>>> + * OTHER DEALINGS IN THE SOFTWARE.
>>> + */
>>> +
>>> +#ifndef __DW_HDMI_RX_PDATA_H__
>>> +#define __DW_HDMI_RX_PDATA_H__
>>> +
>>> +#define DW_HDMI_RX_DRVNAME "dw-hdmi-rx"
>>> +
>>> +/* Notify events */
>>> +#define DW_HDMI_NOTIFY_IS_OFF 1
>>> +#define DW_HDMI_NOTIFY_INPUT_CHANGED 2
>>> +#define DW_HDMI_NOTIFY_AUDIO_CHANGED 3
>>> +#define DW_HDMI_NOTIFY_IS_STABLE 4
>>> +
>>> +/* HDCP 1.4 */
>>> +#define DW_HDMI_HDCP14_BKSV_SIZE 2
>>> +#define DW_HDMI_HDCP14_KEYS_SIZE (2 * 40)
>>> +
>>> +/**
>>> + * struct dw_hdmi_hdcp14_key - HDCP 1.4 keys structure.
>>> + *
>>> + * @seed: Seeed value for HDCP 1.4 engine (16 bits).
>>
>> Typo: Seeed -> Seed
>>
>>> + *
>>> + * @bksv: BKSV value for HDCP 1.4 engine (40 bits).
>>> + *
>>> + * @keys: Keys value for HDCP 1.4 engine (80 * 56 bits).
>>> + *
>>> + * @keys_valid: Must be set to true if the keys in this
>>> structure are valid
>>> + * and can be used by the HDMI receiver controller.
>>> + */
>>> +struct dw_hdmi_hdcp14_key {
>>> + u32 seed;
>>> + u32 bksv[DW_HDMI_HDCP14_BKSV_SIZE];
>>> + u32 keys[DW_HDMI_HDCP14_KEYS_SIZE];
>>> + bool keys_valid;
>>> +};
>>> +
>>> +/**
>>> + * struct dw_hdmi_rx_pdata - Platform Data configuration for
>>> HDMI receiver.
>>> + *
>>> + * @hdcp14_keys: Keys for HDCP 1.4 engine. See
>>> @dw_hdmi_hdcp14_key.
>>
>> Was this for debugging only? These are the Device Private Keys
>> you're talking about?
>>
>> If this is indeed the case, then this doesn't belong here. You
>> should never rely on
>> software to set these keys. It should be fused in the hardware,
>> or read from an
>> encrypted eeprom or something like that. None of this
>> (including the bksv) should
>> be settable from the driver. You can read the bksv since that's
>> public.
>>
>> This can't be in a kernel driver, nor can it be set or read
>> through the s_register API.
>>
>> Instead there should be a big fat disclaimer that how you
>> program these keys is up to
>> the hardware designer and that it should be in accordance to
>> the HDCP requirements.
>>
>> I would drop this completely from the pdata. My recommendation
>> would be to not include
>> HDCP support at all for this first version. Add it in follow-up
>> patches which include
>> a new V4L2 API for handling HDCP. This needs to be handled
>> carefully.
>
> Yes, in real HW these keys will not be handled this way. I'm
> using a prototyping system so its easier to debug. I will remove
> this entirely and drop HDCP 1.4 support for now.
>
> Hmm, I'm seeing the configuration flow for keys written in HW and
> it actually just needs a seed (for encrypted keys, for decrypted
> ones it just doesn't need anything). Shall I drop the support or
> change the code? I've no way to test this right now though...
Drop the support. I don't want to mix this in with the other code. HDCP
support should be done in a separate patch series once this is merged.
That way I can give it the attention it deserves.
Regards,
Hans
>
> Best regards,
> Jose Miguel Abreu
>
>>
>>> + *
>>> + * @dw_5v_status: 5v status callback. Shall return the status
>>> of the given
>>> + * input, i.e. shall be true if a cable is connected to the
>>> specified input.
>>> + *
>>> + * @dw_5v_clear: 5v clear callback. Shall clear the interrupt
>>> associated with
>>> + * the 5v sense controller.
>>> + *
>>> + * @dw_5v_arg: Argument to be used with the 5v sense callbacks.
>>> + *
>>> + * @dw_zcal_reset: Impedance calibration reset callback.
>>> Shall be called when
>>> + * the impedance calibration needs to be restarted. This is
>>> used by phy driver
>>> + * only.
>>> + *
>>> + * @dw_zcal_done: Impendace calibration status callback.
>>> Shall return true if
>>
>> Typo: Impendace -> Impedance
>>
>>> + * the impedance calibration procedure has ended. This is
>>> used by phy driver
>>> + * only.
>>> + *
>>> + * @dw_zcal_arg: Argument to be used with the ZCAL
>>> calibration callbacks.
>>> + */
>>> +struct dw_hdmi_rx_pdata {
>>> + /* Controller configuration */
>>> + struct dw_hdmi_hdcp14_key hdcp14_keys;
>>> + /* 5V sense interface */
>>> + bool (*dw_5v_status)(void __iomem *regs, int input);
>>> + void (*dw_5v_clear)(void __iomem *regs);
>>> + void __iomem *dw_5v_arg;
>>> + /* Zcal interface */
>>> + void (*dw_zcal_reset)(void __iomem *regs);
>>> + bool (*dw_zcal_done)(void __iomem *regs);
>>> + void __iomem *dw_zcal_arg;
>>> +};
>>> +
>>> +#endif /* __DW_HDMI_RX_PDATA_H__ */
>>>
>>
>> Regards,
>>
>> Hans
>
[toc] | [prev] | [next] | [standalone]
| From | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| Date | 2017-07-04 11:30 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZsjD-6nX-11@gated-at.bofh.it> |
| In reply to | #1679870 |
Hi Hans,
On 03-07-2017 11:33, Hans Verkuil wrote:
> On 07/03/2017 11:53 AM, Jose Abreu wrote:
>> Hi Hans,
>>
>>
>> On 03-07-2017 10:27, Hans Verkuil wrote:
>>> On 06/29/2017 12:46 PM, Jose Abreu wrote:
>>>> This is an initial submission for the Synopsys Designware
>>>> HDMI RX
>>>> Controller Driver. This driver interacts with a phy driver so
>>>> that
>>>> a communication between them is created and a video pipeline is
>>>> configured.
>>>>
>>>> The controller + phy pipeline can then be integrated into a
>>>> fully
>>>> featured system that can be able to receive video up to 4k@60Hz
>>>> with deep color 48bit RGB, depending on the platform. Although,
>>>> this initial version does not yet handle deep color modes.
>>>>
>>>> This driver was implemented as a standard V4L2 subdevice and
>>>> its
>>>> main features are:
>>>> - Internal state machine that reconfigures phy until the
>>>> video is not stable
>>>> - JTAG communication with phy
>>>> - Inter-module communication with phy driver
>>>> - Debug write/read ioctls
>>>>
>>>> Some notes:
>>>> - RX sense controller (cable connection/disconnection)
>>>> must
>>>> be handled by the platform wrapper as this is not
>>>> integrated
>>>> into the controller RTL
>>>> - The same goes for EDID ROM's
>>>> - ZCAL calibration is needed only in FPGA platforms, in
>>>> ASIC
>>>> this is not needed
>>>> - The state machine is not an ideal solution as it
>>>> creates a
>>>> kthread but it is needed because some sources might not be
>>>> very stable at sending the video (i.e. we must react
>>>> accordingly).
>>>>
>>>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>>>> Cc: Carlos Palminha <palminha@synopsys.com>
>>>> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
>>>> Cc: Hans Verkuil <hans.verkuil@cisco.com>
>>>> Cc: Sylwester Nawrocki <snawrocki@kernel.org>
>>>>
>>>> Changes from v4:
>>>> - Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
>>>> - Remove some comments and change some messages to dev_dbg
>>>> (Sylwester)
>>>> - Use v4l2_async_subnotifier_register() (Sylwester)
>>>> Changes from v3:
>>>> - Use v4l2 async API (Sylwester)
>>>> - Do not block waiting for phy
>>>> - Do not use busy waiting delays (Sylwester)
>>>> - Simplify dw_hdmi_power_on (Sylwester)
>>>> - Use clock API (Sylwester)
>>>> - Use compatible string (Sylwester)
>>>> - Minor fixes (Sylwester)
>>>> Changes from v2:
>>>> - Address review comments from Hans regarding CEC
>>>> - Use CEC notifier
>>>> - Enable SCDC
>>>> Changes from v1:
>>>> - Add support for CEC
>>>> - Correct typo errors
>>>> - Correctly detect interlaced video modes
>>>> - Correct VIC parsing
>>>> Changes from RFC:
>>>> - Add support for HDCP 1.4
>>>> - Fixup HDMI_VIC not being parsed (Hans)
>>>> - Send source change signal when powering off (Hans)
>>>> - Add a "wait stable delay"
>>>> - Detect interlaced video modes (Hans)
>>>> - Restrain g/s_register from reading/writing to HDCP regs
>>>> (Hans)
>>>> ---
>>>> drivers/media/platform/dwc/Kconfig | 15 +
>>>> drivers/media/platform/dwc/Makefile | 1 +
>>>> drivers/media/platform/dwc/dw-hdmi-rx.c | 1824
>>>> +++++++++++++++++++++++++++++++
>>>> drivers/media/platform/dwc/dw-hdmi-rx.h | 441 ++++++++
>>>> include/media/dwc/dw-hdmi-rx-pdata.h | 97 ++
>>>> 5 files changed, 2378 insertions(+)
>>>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>>>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>>>> create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
>>>>
>>>> diff --git a/drivers/media/platform/dwc/Kconfig
>>>> b/drivers/media/platform/dwc/Kconfig
>>>> index 361d38d..3ddccde 100644
>>>> --- a/drivers/media/platform/dwc/Kconfig
>>>> +++ b/drivers/media/platform/dwc/Kconfig
>>>> @@ -6,3 +6,18 @@ config VIDEO_DWC_HDMI_PHY_E405
>>>> To compile this driver as a module, choose M here.
>>>> The module
>>>> will be called dw-hdmi-phy-e405.
>>>> +
>>>> +config VIDEO_DWC_HDMI_RX
>>>> + tristate "Synopsys Designware HDMI Receiver driver"
>>>> + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
>>>> + help
>>>> + Support for Synopsys Designware HDMI RX controller.
>>>> +
>>>> + To compile this driver as a module, choose M here. The
>>>> module
>>>> + will be called dw-hdmi-rx.
>>>> +
>>>> +config VIDEO_DWC_HDMI_RX_CEC
>>>> + bool
>>>> + depends on VIDEO_DWC_HDMI_RX
>>>> + select CEC_CORE
>>>> + select CEC_NOTIFIER
>>>> diff --git a/drivers/media/platform/dwc/Makefile
>>>> b/drivers/media/platform/dwc/Makefile
>>>> index fc3b62c..cd04ca9 100644
>>>> --- a/drivers/media/platform/dwc/Makefile
>>>> +++ b/drivers/media/platform/dwc/Makefile
>>>> @@ -1 +1,2 @@
>>>> obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
>>>> +obj-$(CONFIG_VIDEO_DWC_HDMI_RX) += dw-hdmi-rx.o
>>>> diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.c
>>>> b/drivers/media/platform/dwc/dw-hdmi-rx.c
>>>> new file mode 100644
>>>> index 0000000..4a7b8fc
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
>>>
>>> <snip>
>
>>>> +static int dw_hdmi_g_register(struct v4l2_subdev *sd,
>>>> + struct v4l2_dbg_register *reg)
>>>> +{
>>>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>>>> +
>>>> + switch (reg->reg >> 15) {
>>>> + case 0: /* Controller core read */
>>>> + if (dw_hdmi_is_reserved_register(dw_dev, reg->reg &
>>>> 0x7fff))
>>>> + return -EINVAL;
>>>
>>> Is this necessary? Obviously you shouldn't be able to set it,
>>> but I think it
>>> should be fine to read it. Up to you, though.
>>
>> Actually some of the HDCP 1.4 registers are write only and if
>> someone tries to read the controller will not respond and will
>> block the bus. This is no problem for x86, but for some archs it
>> can block the system entirely.
>
> Worth a comment in that case.
Ok.
>
>>>> +static const struct v4l2_subdev_video_ops
>>>> dw_hdmi_sd_video_ops = {
>>>> + .s_routing = dw_hdmi_s_routing,
>>>> + .g_input_status = dw_hdmi_g_input_status,
>>>> + .g_parm = dw_hdmi_g_parm,
>>>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>>>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>>>
>>> No s_dv_timings???
>>
>> Hmm, yeah, I didn't implement it because the callchain and the
>> player I use just use {get/set}_fmt. s_dv_timings can just
>> populate the fields and replace them with the detected dv_timings
>> ? Just like set_fmt does? Because the controller has no scaler.
>
> No, s_dv_timings is the function that actually sets
> dw_dev->timings.
> After you check that it is valid of course (call
> v4l2_valid_dv_timings).
>
> set_fmt calls get_fmt which returns the information from
> dw_dev->timings.
>
> But it is s_dv_timings that has to set dw_dev->timings.
>
> With the current code you can only capture 640x480 (the default
> timings).
> Have you ever tested this with any other timings? I don't quite
> understand
> how you test.
I use mpv to test with a wrapper driver that just calls the
subdev ops and sets up a video dma.
Ah, I see now. I failed to port the correct callbacks and in the
upstream version I'm using I only tested with 640x480 ...
But apart from that this is a capture device without scaling so I
can not set timings, I can only return them so that applications
know which format I'm receiving, right? So my s_dv_timings will
return the same as query_dv_timings ...
<snip>
>>>> +
>>>> + /* V4L2 initialization */
>>>> + sd = &dw_dev->sd;
>>>> + v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
>>>> + strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
>>>> + sd->dev = dev;
>>>> + sd->internal_ops = &dw_hdmi_internal_ops;
>>>> + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS |
>>>> V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>
>>> You need to add at this control: V4L2_CID_DV_RX_POWER_PRESENT.
>>> This is a
>>> read-only control that reports the 5V status. Important for
>>> applications to have.
>>
>> Ok.
>>
>>>
>>> I gather that this IP doesn't handle InfoFrames? If it does,
>>> then let me know.
>>
>> Yes, it handles but I didn't implement the parsing yet (I just
>> parse the VIC for now).
>
> Ah, OK. When you add that, then I strongly recommend that you
> also add
> support for the V4L2_CID_DV_RX_RGB_RANGE control, provided this
> IP can
> do quantization range conversion. If quantization range
> conversion is not
> part of this IP, then just ignore this comment.
Hmm, I don't think it can. I mean the controller basically just
outputs what comes from phy in the correct order (it doesn't
touch the bytes, just reorders them and packs them).
<snip>
>>>
>>>> + *
>>>> + * @bksv: BKSV value for HDCP 1.4 engine (40 bits).
>>>> + *
>>>> + * @keys: Keys value for HDCP 1.4 engine (80 * 56 bits).
>>>> + *
>>>> + * @keys_valid: Must be set to true if the keys in this
>>>> structure are valid
>>>> + * and can be used by the HDMI receiver controller.
>>>> + */
>>>> +struct dw_hdmi_hdcp14_key {
>>>> + u32 seed;
>>>> + u32 bksv[DW_HDMI_HDCP14_BKSV_SIZE];
>>>> + u32 keys[DW_HDMI_HDCP14_KEYS_SIZE];
>>>> + bool keys_valid;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct dw_hdmi_rx_pdata - Platform Data configuration for
>>>> HDMI receiver.
>>>> + *
>>>> + * @hdcp14_keys: Keys for HDCP 1.4 engine. See
>>>> @dw_hdmi_hdcp14_key.
>>>
>>> Was this for debugging only? These are the Device Private Keys
>>> you're talking about?
>>>
>>> If this is indeed the case, then this doesn't belong here. You
>>> should never rely on
>>> software to set these keys. It should be fused in the hardware,
>>> or read from an
>>> encrypted eeprom or something like that. None of this
>>> (including the bksv) should
>>> be settable from the driver. You can read the bksv since that's
>>> public.
>>>
>>> This can't be in a kernel driver, nor can it be set or read
>>> through the s_register API.
>>>
>>> Instead there should be a big fat disclaimer that how you
>>> program these keys is up to
>>> the hardware designer and that it should be in accordance to
>>> the HDCP requirements.
>>>
>>> I would drop this completely from the pdata. My recommendation
>>> would be to not include
>>> HDCP support at all for this first version. Add it in follow-up
>>> patches which include
>>> a new V4L2 API for handling HDCP. This needs to be handled
>>> carefully.
>>
>> Yes, in real HW these keys will not be handled this way. I'm
>> using a prototyping system so its easier to debug. I will remove
>> this entirely and drop HDCP 1.4 support for now.
>>
>> Hmm, I'm seeing the configuration flow for keys written in HW and
>> it actually just needs a seed (for encrypted keys, for decrypted
>> ones it just doesn't need anything). Shall I drop the support or
>> change the code? I've no way to test this right now though...
>
> Drop the support. I don't want to mix this in with the other
> code. HDCP
> support should be done in a separate patch series once this is
> merged.
> That way I can give it the attention it deserves.
Ok.
Best regards,
Jose Miguel Abreu
>
> Regards,
>
> Hans
>
>>
>> Best regards,
>> Jose Miguel Abreu
>>
>>>
>>>> + *
>>>> + * @dw_5v_status: 5v status callback. Shall return the status
>>>> of the given
>>>> + * input, i.e. shall be true if a cable is connected to the
>>>> specified input.
>>>> + *
>>>> + * @dw_5v_clear: 5v clear callback. Shall clear the interrupt
>>>> associated with
>>>> + * the 5v sense controller.
>>>> + *
>>>> + * @dw_5v_arg: Argument to be used with the 5v sense
>>>> callbacks.
>>>> + *
>>>> + * @dw_zcal_reset: Impedance calibration reset callback.
>>>> Shall be called when
>>>> + * the impedance calibration needs to be restarted. This is
>>>> used by phy driver
>>>> + * only.
>>>> + *
>>>> + * @dw_zcal_done: Impendace calibration status callback.
>>>> Shall return true if
>>>
>>> Typo: Impendace -> Impedance
>>>
>>>> + * the impedance calibration procedure has ended. This is
>>>> used by phy driver
>>>> + * only.
>>>> + *
>>>> + * @dw_zcal_arg: Argument to be used with the ZCAL
>>>> calibration callbacks.
>>>> + */
>>>> +struct dw_hdmi_rx_pdata {
>>>> + /* Controller configuration */
>>>> + struct dw_hdmi_hdcp14_key hdcp14_keys;
>>>> + /* 5V sense interface */
>>>> + bool (*dw_5v_status)(void __iomem *regs, int input);
>>>> + void (*dw_5v_clear)(void __iomem *regs);
>>>> + void __iomem *dw_5v_arg;
>>>> + /* Zcal interface */
>>>> + void (*dw_zcal_reset)(void __iomem *regs);
>>>> + bool (*dw_zcal_done)(void __iomem *regs);
>>>> + void __iomem *dw_zcal_arg;
>>>> +};
>>>> +
>>>> +#endif /* __DW_HDMI_RX_PDATA_H__ */
>>>>
>>>
>>> Regards,
>>>
>>> Hans
>>
>
[toc] | [prev] | [next] | [standalone]
| From | Hans Verkuil <hansverk@cisco.com> |
|---|---|
| Date | 2017-07-04 11:50 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZsD0-6u0-9@gated-at.bofh.it> |
| In reply to | #1680797 |
On 07/04/17 11:28, Jose Abreu wrote:
> Hi Hans,
>
>
> On 03-07-2017 11:33, Hans Verkuil wrote:
>> On 07/03/2017 11:53 AM, Jose Abreu wrote:
>>> Hi Hans,
>>>
>>>
>>> On 03-07-2017 10:27, Hans Verkuil wrote:
>>>> On 06/29/2017 12:46 PM, Jose Abreu wrote:
>>>>> This is an initial submission for the Synopsys Designware
>>>>> HDMI RX
>>>>> Controller Driver. This driver interacts with a phy driver so
>>>>> that
>>>>> a communication between them is created and a video pipeline is
>>>>> configured.
>>>>>
>>>>> The controller + phy pipeline can then be integrated into a
>>>>> fully
>>>>> featured system that can be able to receive video up to 4k@60Hz
>>>>> with deep color 48bit RGB, depending on the platform. Although,
>>>>> this initial version does not yet handle deep color modes.
>>>>>
>>>>> This driver was implemented as a standard V4L2 subdevice and
>>>>> its
>>>>> main features are:
>>>>> - Internal state machine that reconfigures phy until the
>>>>> video is not stable
>>>>> - JTAG communication with phy
>>>>> - Inter-module communication with phy driver
>>>>> - Debug write/read ioctls
>>>>>
>>>>> Some notes:
>>>>> - RX sense controller (cable connection/disconnection)
>>>>> must
>>>>> be handled by the platform wrapper as this is not
>>>>> integrated
>>>>> into the controller RTL
>>>>> - The same goes for EDID ROM's
>>>>> - ZCAL calibration is needed only in FPGA platforms, in
>>>>> ASIC
>>>>> this is not needed
>>>>> - The state machine is not an ideal solution as it
>>>>> creates a
>>>>> kthread but it is needed because some sources might not be
>>>>> very stable at sending the video (i.e. we must react
>>>>> accordingly).
>>>>>
>>>>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>>>>> Cc: Carlos Palminha <palminha@synopsys.com>
>>>>> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
>>>>> Cc: Hans Verkuil <hans.verkuil@cisco.com>
>>>>> Cc: Sylwester Nawrocki <snawrocki@kernel.org>
>>>>>
>>>>> Changes from v4:
>>>>> - Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
>>>>> - Remove some comments and change some messages to dev_dbg
>>>>> (Sylwester)
>>>>> - Use v4l2_async_subnotifier_register() (Sylwester)
>>>>> Changes from v3:
>>>>> - Use v4l2 async API (Sylwester)
>>>>> - Do not block waiting for phy
>>>>> - Do not use busy waiting delays (Sylwester)
>>>>> - Simplify dw_hdmi_power_on (Sylwester)
>>>>> - Use clock API (Sylwester)
>>>>> - Use compatible string (Sylwester)
>>>>> - Minor fixes (Sylwester)
>>>>> Changes from v2:
>>>>> - Address review comments from Hans regarding CEC
>>>>> - Use CEC notifier
>>>>> - Enable SCDC
>>>>> Changes from v1:
>>>>> - Add support for CEC
>>>>> - Correct typo errors
>>>>> - Correctly detect interlaced video modes
>>>>> - Correct VIC parsing
>>>>> Changes from RFC:
>>>>> - Add support for HDCP 1.4
>>>>> - Fixup HDMI_VIC not being parsed (Hans)
>>>>> - Send source change signal when powering off (Hans)
>>>>> - Add a "wait stable delay"
>>>>> - Detect interlaced video modes (Hans)
>>>>> - Restrain g/s_register from reading/writing to HDCP regs
>>>>> (Hans)
>>>>> ---
>>>>> drivers/media/platform/dwc/Kconfig | 15 +
>>>>> drivers/media/platform/dwc/Makefile | 1 +
>>>>> drivers/media/platform/dwc/dw-hdmi-rx.c | 1824
>>>>> +++++++++++++++++++++++++++++++
>>>>> drivers/media/platform/dwc/dw-hdmi-rx.h | 441 ++++++++
>>>>> include/media/dwc/dw-hdmi-rx-pdata.h | 97 ++
>>>>> 5 files changed, 2378 insertions(+)
>>>>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>>>>> create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>>>>> create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
>>>>>
>>>>> diff --git a/drivers/media/platform/dwc/Kconfig
>>>>> b/drivers/media/platform/dwc/Kconfig
>>>>> index 361d38d..3ddccde 100644
>>>>> --- a/drivers/media/platform/dwc/Kconfig
>>>>> +++ b/drivers/media/platform/dwc/Kconfig
>>>>> @@ -6,3 +6,18 @@ config VIDEO_DWC_HDMI_PHY_E405
>>>>> To compile this driver as a module, choose M here.
>>>>> The module
>>>>> will be called dw-hdmi-phy-e405.
>>>>> +
>>>>> +config VIDEO_DWC_HDMI_RX
>>>>> + tristate "Synopsys Designware HDMI Receiver driver"
>>>>> + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
>>>>> + help
>>>>> + Support for Synopsys Designware HDMI RX controller.
>>>>> +
>>>>> + To compile this driver as a module, choose M here. The
>>>>> module
>>>>> + will be called dw-hdmi-rx.
>>>>> +
>>>>> +config VIDEO_DWC_HDMI_RX_CEC
>>>>> + bool
>>>>> + depends on VIDEO_DWC_HDMI_RX
>>>>> + select CEC_CORE
>>>>> + select CEC_NOTIFIER
>>>>> diff --git a/drivers/media/platform/dwc/Makefile
>>>>> b/drivers/media/platform/dwc/Makefile
>>>>> index fc3b62c..cd04ca9 100644
>>>>> --- a/drivers/media/platform/dwc/Makefile
>>>>> +++ b/drivers/media/platform/dwc/Makefile
>>>>> @@ -1 +1,2 @@
>>>>> obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
>>>>> +obj-$(CONFIG_VIDEO_DWC_HDMI_RX) += dw-hdmi-rx.o
>>>>> diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.c
>>>>> b/drivers/media/platform/dwc/dw-hdmi-rx.c
>>>>> new file mode 100644
>>>>> index 0000000..4a7b8fc
>>>>> --- /dev/null
>>>>> +++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
>>>>
>>>> <snip>
>>
>>>>> +static int dw_hdmi_g_register(struct v4l2_subdev *sd,
>>>>> + struct v4l2_dbg_register *reg)
>>>>> +{
>>>>> + struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>>>>> +
>>>>> + switch (reg->reg >> 15) {
>>>>> + case 0: /* Controller core read */
>>>>> + if (dw_hdmi_is_reserved_register(dw_dev, reg->reg &
>>>>> 0x7fff))
>>>>> + return -EINVAL;
>>>>
>>>> Is this necessary? Obviously you shouldn't be able to set it,
>>>> but I think it
>>>> should be fine to read it. Up to you, though.
>>>
>>> Actually some of the HDCP 1.4 registers are write only and if
>>> someone tries to read the controller will not respond and will
>>> block the bus. This is no problem for x86, but for some archs it
>>> can block the system entirely.
>>
>> Worth a comment in that case.
>
> Ok.
>
>>
>>>>> +static const struct v4l2_subdev_video_ops
>>>>> dw_hdmi_sd_video_ops = {
>>>>> + .s_routing = dw_hdmi_s_routing,
>>>>> + .g_input_status = dw_hdmi_g_input_status,
>>>>> + .g_parm = dw_hdmi_g_parm,
>>>>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>>>>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>>>>
>>>> No s_dv_timings???
>>>
>>> Hmm, yeah, I didn't implement it because the callchain and the
>>> player I use just use {get/set}_fmt. s_dv_timings can just
>>> populate the fields and replace them with the detected dv_timings
>>> ? Just like set_fmt does? Because the controller has no scaler.
>>
>> No, s_dv_timings is the function that actually sets
>> dw_dev->timings.
>> After you check that it is valid of course (call
>> v4l2_valid_dv_timings).
>>
>> set_fmt calls get_fmt which returns the information from
>> dw_dev->timings.
>>
>> But it is s_dv_timings that has to set dw_dev->timings.
>>
>> With the current code you can only capture 640x480 (the default
>> timings).
>> Have you ever tested this with any other timings? I don't quite
>> understand
>> how you test.
>
> I use mpv to test with a wrapper driver that just calls the
> subdev ops and sets up a video dma.
>
> Ah, I see now. I failed to port the correct callbacks and in the
> upstream version I'm using I only tested with 640x480 ...
>
> But apart from that this is a capture device without scaling so I
> can not set timings, I can only return them so that applications
> know which format I'm receiving, right? So my s_dv_timings will
> return the same as query_dv_timings ...
Well, to be precise: s_dv_timings just accepts what the application
gives it (as long as it is within the dv_timings capabilities). But
those timings come in practice from a query_dv_timings call from the
application.
The core rule is that receivers cannot randomly change timings since
timings are related to buffer sizes. You do not want the application
to allocate buffers for 640x480 and when the source changes to 1920x1080
have those buffers suddenly overflow.
Instead the app queries the timings, allocates the buffers, start
streaming and when the timings change it will get an event so it can
stop streaming, reallocate buffers, and start the process again.
In other words, the application is in control here.
>
> <snip>
>
>>>>> +
>>>>> + /* V4L2 initialization */
>>>>> + sd = &dw_dev->sd;
>>>>> + v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
>>>>> + strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
>>>>> + sd->dev = dev;
>>>>> + sd->internal_ops = &dw_hdmi_internal_ops;
>>>>> + sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS |
>>>>> V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>>
>>>> You need to add at this control: V4L2_CID_DV_RX_POWER_PRESENT.
>>>> This is a
>>>> read-only control that reports the 5V status. Important for
>>>> applications to have.
>>>
>>> Ok.
>>>
>>>>
>>>> I gather that this IP doesn't handle InfoFrames? If it does,
>>>> then let me know.
>>>
>>> Yes, it handles but I didn't implement the parsing yet (I just
>>> parse the VIC for now).
>>
>> Ah, OK. When you add that, then I strongly recommend that you
>> also add
>> support for the V4L2_CID_DV_RX_RGB_RANGE control, provided this
>> IP can
>> do quantization range conversion. If quantization range
>> conversion is not
>> part of this IP, then just ignore this comment.
>
> Hmm, I don't think it can. I mean the controller basically just
> outputs what comes from phy in the correct order (it doesn't
> touch the bytes, just reorders them and packs them).
I suspected as much. So just ignore this.
Regards,
Hans
[toc] | [prev] | [next] | [standalone]
| From | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| Date | 2017-07-04 14:40 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZvhw-8cI-15@gated-at.bofh.it> |
| In reply to | #1680815 |
On 04-07-2017 10:39, Hans Verkuil wrote:
>
>>>>>> +static const struct v4l2_subdev_video_ops
>>>>>> dw_hdmi_sd_video_ops = {
>>>>>> + .s_routing = dw_hdmi_s_routing,
>>>>>> + .g_input_status = dw_hdmi_g_input_status,
>>>>>> + .g_parm = dw_hdmi_g_parm,
>>>>>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>>>>>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>>>>> No s_dv_timings???
>>>> Hmm, yeah, I didn't implement it because the callchain and the
>>>> player I use just use {get/set}_fmt. s_dv_timings can just
>>>> populate the fields and replace them with the detected dv_timings
>>>> ? Just like set_fmt does? Because the controller has no scaler.
>>> No, s_dv_timings is the function that actually sets
>>> dw_dev->timings.
>>> After you check that it is valid of course (call
>>> v4l2_valid_dv_timings).
>>>
>>> set_fmt calls get_fmt which returns the information from
>>> dw_dev->timings.
>>>
>>> But it is s_dv_timings that has to set dw_dev->timings.
>>>
>>> With the current code you can only capture 640x480 (the default
>>> timings).
>>> Have you ever tested this with any other timings? I don't quite
>>> understand
>>> how you test.
>> I use mpv to test with a wrapper driver that just calls the
>> subdev ops and sets up a video dma.
>>
>> Ah, I see now. I failed to port the correct callbacks and in the
>> upstream version I'm using I only tested with 640x480 ...
>>
>> But apart from that this is a capture device without scaling so I
>> can not set timings, I can only return them so that applications
>> know which format I'm receiving, right? So my s_dv_timings will
>> return the same as query_dv_timings ...
> Well, to be precise: s_dv_timings just accepts what the application
> gives it (as long as it is within the dv_timings capabilities). But
> those timings come in practice from a query_dv_timings call from the
> application.
>
> The core rule is that receivers cannot randomly change timings since
> timings are related to buffer sizes. You do not want the application
> to allocate buffers for 640x480 and when the source changes to 1920x1080
> have those buffers suddenly overflow.
>
> Instead the app queries the timings, allocates the buffers, start
> streaming and when the timings change it will get an event so it can
> stop streaming, reallocate buffers, and start the process again.
>
> In other words, the application is in control here.
>
... But this is not true for mpv/mplayer. They first try to set a
default format (by using s_fmt) and then query the format again
(by using g_fmt) ... So dv_timings are never used. Are these apps
broken? Im only using them because of performance, do you
recommend others?
Best regards,
Jose Miguel Abreu
[toc] | [prev] | [next] | [standalone]
| From | Hans Verkuil <hansverk@cisco.com> |
|---|---|
| Date | 2017-07-04 15:10 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZvKz-bO-35@gated-at.bofh.it> |
| In reply to | #1680920 |
On 07/04/17 14:33, Jose Abreu wrote:
>
>
> On 04-07-2017 10:39, Hans Verkuil wrote:
>>
>>>>>>> +static const struct v4l2_subdev_video_ops
>>>>>>> dw_hdmi_sd_video_ops = {
>>>>>>> + .s_routing = dw_hdmi_s_routing,
>>>>>>> + .g_input_status = dw_hdmi_g_input_status,
>>>>>>> + .g_parm = dw_hdmi_g_parm,
>>>>>>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>>>>>>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>>>>>> No s_dv_timings???
>>>>> Hmm, yeah, I didn't implement it because the callchain and the
>>>>> player I use just use {get/set}_fmt. s_dv_timings can just
>>>>> populate the fields and replace them with the detected dv_timings
>>>>> ? Just like set_fmt does? Because the controller has no scaler.
>>>> No, s_dv_timings is the function that actually sets
>>>> dw_dev->timings.
>>>> After you check that it is valid of course (call
>>>> v4l2_valid_dv_timings).
>>>>
>>>> set_fmt calls get_fmt which returns the information from
>>>> dw_dev->timings.
>>>>
>>>> But it is s_dv_timings that has to set dw_dev->timings.
>>>>
>>>> With the current code you can only capture 640x480 (the default
>>>> timings).
>>>> Have you ever tested this with any other timings? I don't quite
>>>> understand
>>>> how you test.
>>> I use mpv to test with a wrapper driver that just calls the
>>> subdev ops and sets up a video dma.
>>>
>>> Ah, I see now. I failed to port the correct callbacks and in the
>>> upstream version I'm using I only tested with 640x480 ...
>>>
>>> But apart from that this is a capture device without scaling so I
>>> can not set timings, I can only return them so that applications
>>> know which format I'm receiving, right? So my s_dv_timings will
>>> return the same as query_dv_timings ...
>> Well, to be precise: s_dv_timings just accepts what the application
>> gives it (as long as it is within the dv_timings capabilities). But
>> those timings come in practice from a query_dv_timings call from the
>> application.
>>
>> The core rule is that receivers cannot randomly change timings since
>> timings are related to buffer sizes. You do not want the application
>> to allocate buffers for 640x480 and when the source changes to 1920x1080
>> have those buffers suddenly overflow.
>>
>> Instead the app queries the timings, allocates the buffers, start
>> streaming and when the timings change it will get an event so it can
>> stop streaming, reallocate buffers, and start the process again.
>>
>> In other words, the application is in control here.
>>
>
> ... But this is not true for mpv/mplayer. They first try to set a
> default format (by using s_fmt) and then query the format again
> (by using g_fmt) ... So dv_timings are never used. Are these apps
> broken? Im only using them because of performance, do you
> recommend others?
I don't believe those have ever been adapted to the DV_TIMINGS API. Only
SDTV (G/S/QUERYSTD). I believe gstreamer can handle this, though. But I
don't have any experience with gstreamer.
qv4l2 works fine, though. If you can build that on your system, then that's
by far the easiest utility to use.
The reason why so few applications have been adapted to the DV_TIMINGS API
is that it is so hard to get hardware with working HDMI input. There are
PCIe cards, but since the datasheets for the used HDMI receivers are closed
we can't make a driver. And there are no cheap SoC devkits that have HDMI input.
Output, yes. Input, no.
So there is no easy way to add support for this to applications.
Regards,
Hans
[toc] | [prev] | [next] | [standalone]
| From | Jose Abreu <Jose.Abreu@synopsys.com> |
|---|---|
| Date | 2017-07-04 16:00 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZwwV-tH-21@gated-at.bofh.it> |
| In reply to | #1680968 |
On 04-07-2017 14:02, Hans Verkuil wrote:
> On 07/04/17 14:33, Jose Abreu wrote:
>>
>> On 04-07-2017 10:39, Hans Verkuil wrote:
>>>>>>>> +static const struct v4l2_subdev_video_ops
>>>>>>>> dw_hdmi_sd_video_ops = {
>>>>>>>> + .s_routing = dw_hdmi_s_routing,
>>>>>>>> + .g_input_status = dw_hdmi_g_input_status,
>>>>>>>> + .g_parm = dw_hdmi_g_parm,
>>>>>>>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>>>>>>>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>>>>>>> No s_dv_timings???
>>>>>> Hmm, yeah, I didn't implement it because the callchain and the
>>>>>> player I use just use {get/set}_fmt. s_dv_timings can just
>>>>>> populate the fields and replace them with the detected dv_timings
>>>>>> ? Just like set_fmt does? Because the controller has no scaler.
>>>>> No, s_dv_timings is the function that actually sets
>>>>> dw_dev->timings.
>>>>> After you check that it is valid of course (call
>>>>> v4l2_valid_dv_timings).
>>>>>
>>>>> set_fmt calls get_fmt which returns the information from
>>>>> dw_dev->timings.
>>>>>
>>>>> But it is s_dv_timings that has to set dw_dev->timings.
>>>>>
>>>>> With the current code you can only capture 640x480 (the default
>>>>> timings).
>>>>> Have you ever tested this with any other timings? I don't quite
>>>>> understand
>>>>> how you test.
>>>> I use mpv to test with a wrapper driver that just calls the
>>>> subdev ops and sets up a video dma.
>>>>
>>>> Ah, I see now. I failed to port the correct callbacks and in the
>>>> upstream version I'm using I only tested with 640x480 ...
>>>>
>>>> But apart from that this is a capture device without scaling so I
>>>> can not set timings, I can only return them so that applications
>>>> know which format I'm receiving, right? So my s_dv_timings will
>>>> return the same as query_dv_timings ...
>>> Well, to be precise: s_dv_timings just accepts what the application
>>> gives it (as long as it is within the dv_timings capabilities). But
>>> those timings come in practice from a query_dv_timings call from the
>>> application.
>>>
>>> The core rule is that receivers cannot randomly change timings since
>>> timings are related to buffer sizes. You do not want the application
>>> to allocate buffers for 640x480 and when the source changes to 1920x1080
>>> have those buffers suddenly overflow.
>>>
>>> Instead the app queries the timings, allocates the buffers, start
>>> streaming and when the timings change it will get an event so it can
>>> stop streaming, reallocate buffers, and start the process again.
>>>
>>> In other words, the application is in control here.
>>>
>> ... But this is not true for mpv/mplayer. They first try to set a
>> default format (by using s_fmt) and then query the format again
>> (by using g_fmt) ... So dv_timings are never used. Are these apps
>> broken? Im only using them because of performance, do you
>> recommend others?
> I don't believe those have ever been adapted to the DV_TIMINGS API. Only
> SDTV (G/S/QUERYSTD). I believe gstreamer can handle this, though. But I
> don't have any experience with gstreamer.
>
> qv4l2 works fine, though. If you can build that on your system, then that's
> by far the easiest utility to use.
I will give it a try in my PCIe setup. But for my embedded setup
(the one I'm using for testing this controller) it won't do. To
handle this I modified my wrapper driver to "simulate" the
dv_timings calls.
>
> The reason why so few applications have been adapted to the DV_TIMINGS API
> is that it is so hard to get hardware with working HDMI input. There are
> PCIe cards, but since the datasheets for the used HDMI receivers are closed
> we can't make a driver. And there are no cheap SoC devkits that have HDMI input.
> Output, yes. Input, no.
>
> So there is no easy way to add support for this to applications.
>
> Regards,
>
> Hans
Yeah, and even for SDTV the support is limited in mpv/mplayer :/
I was told that v4l2 code in these apps is more or less unmaintained.
BTW, do you have any pending comments for the other patches in
this series? Because I've addressed all your comments regarding
this patch and I'm ready to send a new version.
Best regards,
Jose Miguel Abreu
[toc] | [prev] | [next] | [standalone]
| From | Hans Verkuil <hansverk@cisco.com> |
|---|---|
| Date | 2017-07-04 16:10 +0200 |
| Subject | Re: [PATCH v5 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver |
| Message-ID | <tZwGB-Oa-9@gated-at.bofh.it> |
| In reply to | #1680991 |
On 07/04/17 15:50, Jose Abreu wrote:
>
>
> On 04-07-2017 14:02, Hans Verkuil wrote:
>> On 07/04/17 14:33, Jose Abreu wrote:
>>>
>>> On 04-07-2017 10:39, Hans Verkuil wrote:
>>>>>>>>> +static const struct v4l2_subdev_video_ops
>>>>>>>>> dw_hdmi_sd_video_ops = {
>>>>>>>>> + .s_routing = dw_hdmi_s_routing,
>>>>>>>>> + .g_input_status = dw_hdmi_g_input_status,
>>>>>>>>> + .g_parm = dw_hdmi_g_parm,
>>>>>>>>> + .g_dv_timings = dw_hdmi_g_dv_timings,
>>>>>>>>> + .query_dv_timings = dw_hdmi_query_dv_timings,
>>>>>>>> No s_dv_timings???
>>>>>>> Hmm, yeah, I didn't implement it because the callchain and the
>>>>>>> player I use just use {get/set}_fmt. s_dv_timings can just
>>>>>>> populate the fields and replace them with the detected dv_timings
>>>>>>> ? Just like set_fmt does? Because the controller has no scaler.
>>>>>> No, s_dv_timings is the function that actually sets
>>>>>> dw_dev->timings.
>>>>>> After you check that it is valid of course (call
>>>>>> v4l2_valid_dv_timings).
>>>>>>
>>>>>> set_fmt calls get_fmt which returns the information from
>>>>>> dw_dev->timings.
>>>>>>
>>>>>> But it is s_dv_timings that has to set dw_dev->timings.
>>>>>>
>>>>>> With the current code you can only capture 640x480 (the default
>>>>>> timings).
>>>>>> Have you ever tested this with any other timings? I don't quite
>>>>>> understand
>>>>>> how you test.
>>>>> I use mpv to test with a wrapper driver that just calls the
>>>>> subdev ops and sets up a video dma.
>>>>>
>>>>> Ah, I see now. I failed to port the correct callbacks and in the
>>>>> upstream version I'm using I only tested with 640x480 ...
>>>>>
>>>>> But apart from that this is a capture device without scaling so I
>>>>> can not set timings, I can only return them so that applications
>>>>> know which format I'm receiving, right? So my s_dv_timings will
>>>>> return the same as query_dv_timings ...
>>>> Well, to be precise: s_dv_timings just accepts what the application
>>>> gives it (as long as it is within the dv_timings capabilities). But
>>>> those timings come in practice from a query_dv_timings call from the
>>>> application.
>>>>
>>>> The core rule is that receivers cannot randomly change timings since
>>>> timings are related to buffer sizes. You do not want the application
>>>> to allocate buffers for 640x480 and when the source changes to 1920x1080
>>>> have those buffers suddenly overflow.
>>>>
>>>> Instead the app queries the timings, allocates the buffers, start
>>>> streaming and when the timings change it will get an event so it can
>>>> stop streaming, reallocate buffers, and start the process again.
>>>>
>>>> In other words, the application is in control here.
>>>>
>>> ... But this is not true for mpv/mplayer. They first try to set a
>>> default format (by using s_fmt) and then query the format again
>>> (by using g_fmt) ... So dv_timings are never used. Are these apps
>>> broken? Im only using them because of performance, do you
>>> recommend others?
>> I don't believe those have ever been adapted to the DV_TIMINGS API. Only
>> SDTV (G/S/QUERYSTD). I believe gstreamer can handle this, though. But I
>> don't have any experience with gstreamer.
>>
>> qv4l2 works fine, though. If you can build that on your system, then that's
>> by far the easiest utility to use.
>
> I will give it a try in my PCIe setup. But for my embedded setup
> (the one I'm using for testing this controller) it won't do. To
> handle this I modified my wrapper driver to "simulate" the
> dv_timings calls.
>
>>
>> The reason why so few applications have been adapted to the DV_TIMINGS API
>> is that it is so hard to get hardware with working HDMI input. There are
>> PCIe cards, but since the datasheets for the used HDMI receivers are closed
>> we can't make a driver. And there are no cheap SoC devkits that have HDMI input.
>> Output, yes. Input, no.
>>
>> So there is no easy way to add support for this to applications.
>>
>> Regards,
>>
>> Hans
>
> Yeah, and even for SDTV the support is limited in mpv/mplayer :/
> I was told that v4l2 code in these apps is more or less unmaintained.
For the average PC user video capture is simply not all that interesting
or relevant anymore. Video capture moved to embedded systems (phones,
tablets, but of course also video conferencing equipment) and professional
video processing.
> BTW, do you have any pending comments for the other patches in
> this series? Because I've addressed all your comments regarding
> this patch and I'm ready to send a new version.
No, I didn't have any comments for the other patches.
Regards,
Hans
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web