Re: [PATCH v3 6/7] drm/i2c: tda998x: add CEC support

2018-04-24 Thread Hans Verkuil
On 04/09/18 14:16, Russell King wrote:
> The TDA998x is a HDMI transmitter with a TDA9950 CEC engine integrated
> onto the same die.  Add support for the TDA9950 CEC engine to the
> TDA998x driver.
> 
> Signed-off-by: Russell King 

Reviewed-by: Hans Verkuil 

Regards,

Hans

> ---
>  drivers/gpu/drm/i2c/Kconfig   |   1 +
>  drivers/gpu/drm/i2c/tda998x_drv.c | 195 
> --
>  2 files changed, 187 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
> index 3a232f5ff0a1..65d3acb61c03 100644
> --- a/drivers/gpu/drm/i2c/Kconfig
> +++ b/drivers/gpu/drm/i2c/Kconfig
> @@ -22,6 +22,7 @@ config DRM_I2C_SIL164
>  config DRM_I2C_NXP_TDA998X
>   tristate "NXP Semiconductors TDA998X HDMI encoder"
>   default m if DRM_TILCDC
> + select CEC_CORE if CEC_NOTIFIER
>   select SND_SOC_HDMI_CODEC if SND_SOC
>   help
> Support for NXP Semiconductors TDA998X HDMI encoders.
> diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c 
> b/drivers/gpu/drm/i2c/tda998x_drv.c
> index 16e0439cad44..eb9916bd84a4 100644
> --- a/drivers/gpu/drm/i2c/tda998x_drv.c
> +++ b/drivers/gpu/drm/i2c/tda998x_drv.c
> @@ -16,8 +16,10 @@
>   */
>  
>  #include 
> +#include 
>  #include 
>  #include 
> +#include 
>  #include 
>  #include 
>  #include 
> @@ -29,6 +31,8 @@
>  #include 
>  #include 
>  
> +#include 
> +
>  #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
>  
>  struct tda998x_audio_port {
> @@ -55,6 +59,7 @@ struct tda998x_priv {
>   struct platform_device *audio_pdev;
>   struct mutex audio_mutex;
>  
> + struct mutex edid_mutex;
>   wait_queue_head_t wq_edid;
>   volatile int wq_edid_wait;
>  
> @@ -67,6 +72,9 @@ struct tda998x_priv {
>   struct drm_connector connector;
>  
>   struct tda998x_audio_port audio_port[2];
> + struct tda9950_glue cec_glue;
> + struct gpio_desc *calib;
> + struct cec_notifier *cec_notify;
>  };
>  
>  #define conn_to_tda998x_priv(x) \
> @@ -345,6 +353,12 @@ struct tda998x_priv {
>  #define REG_CEC_INTSTATUS  0xee/* read */
>  # define CEC_INTSTATUS_CEC (1 << 0)
>  # define CEC_INTSTATUS_HDMI(1 << 1)
> +#define REG_CEC_CAL_XOSC_CTRL10xf2
> +# define CEC_CAL_XOSC_CTRL1_ENA_CAL  BIT(0)
> +#define REG_CEC_DES_FREQ2 0xf5
> +# define CEC_DES_FREQ2_DIS_AUTOCAL BIT(7)
> +#define REG_CEC_CLK   0xf6
> +# define CEC_CLK_FRO  0x11
>  #define REG_CEC_FRO_IM_CLK_CTRL   0xfb/* read/write */
>  # define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
>  # define CEC_FRO_IM_CLK_CTRL_ENA_OTP   (1 << 6)
> @@ -359,6 +373,7 @@ struct tda998x_priv {
>  # define CEC_RXSHPDLEV_HPD(1 << 1)
>  
>  #define REG_CEC_ENAMODS   0xff/* read/write */
> +# define CEC_ENAMODS_EN_CEC_CLK   (1 << 7)
>  # define CEC_ENAMODS_DIS_FRO  (1 << 6)
>  # define CEC_ENAMODS_DIS_CCLK (1 << 5)
>  # define CEC_ENAMODS_EN_RXSENS(1 << 2)
> @@ -417,6 +432,114 @@ cec_read(struct tda998x_priv *priv, u8 addr)
>   return val;
>  }
>  
> +static void cec_enamods(struct tda998x_priv *priv, u8 mods, bool enable)
> +{
> + int val = cec_read(priv, REG_CEC_ENAMODS);
> +
> + if (val < 0)
> + return;
> +
> + if (enable)
> + val |= mods;
> + else
> + val &= ~mods;
> +
> + cec_write(priv, REG_CEC_ENAMODS, val);
> +}
> +
> +static void tda998x_cec_set_calibration(struct tda998x_priv *priv, bool 
> enable)
> +{
> + if (enable) {
> + u8 val;
> +
> + cec_write(priv, 0xf3, 0xc0);
> + cec_write(priv, 0xf4, 0xd4);
> +
> + /* Enable automatic calibration mode */
> + val = cec_read(priv, REG_CEC_DES_FREQ2);
> + val &= ~CEC_DES_FREQ2_DIS_AUTOCAL;
> + cec_write(priv, REG_CEC_DES_FREQ2, val);
> +
> + /* Enable free running oscillator */
> + cec_write(priv, REG_CEC_CLK, CEC_CLK_FRO);
> + cec_enamods(priv, CEC_ENAMODS_DIS_FRO, false);
> +
> + cec_write(priv, REG_CEC_CAL_XOSC_CTRL1,
> +   CEC_CAL_XOSC_CTRL1_ENA_CAL);
> + } else {
> + cec_write(priv, REG_CEC_CAL_XOSC_CTRL1, 0);
> + }
> +}
> +
> +/*
> + * Calibration for the internal oscillator: we need to set calibration mode,
> + * and then pulse the IRQ line low for a 10ms ± 1% period.
> + */
> +static void tda998x_cec_calibration(struct tda998x_priv *priv)
> +{
> + struct gpio_desc *calib = priv->calib;
> +
> + mutex_lock(>edid_mutex);
> + if (priv->hdmi->irq > 0)
> + disable_irq(priv->hdmi->irq);
> + gpiod_direction_output(calib, 1);
> + tda998x_cec_set_calibration(priv, true);
> +
> + local_irq_disable();
> + gpiod_set_value(calib, 0);
> + mdelay(10);
> + gpiod_set_value(calib, 1);
> + local_irq_enable();
> +
> + 

[PATCH v3 6/7] drm/i2c: tda998x: add CEC support

2018-04-10 Thread Russell King
The TDA998x is a HDMI transmitter with a TDA9950 CEC engine integrated
onto the same die.  Add support for the TDA9950 CEC engine to the
TDA998x driver.

Signed-off-by: Russell King 
---
 drivers/gpu/drm/i2c/Kconfig   |   1 +
 drivers/gpu/drm/i2c/tda998x_drv.c | 195 --
 2 files changed, 187 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
index 3a232f5ff0a1..65d3acb61c03 100644
--- a/drivers/gpu/drm/i2c/Kconfig
+++ b/drivers/gpu/drm/i2c/Kconfig
@@ -22,6 +22,7 @@ config DRM_I2C_SIL164
 config DRM_I2C_NXP_TDA998X
tristate "NXP Semiconductors TDA998X HDMI encoder"
default m if DRM_TILCDC
+   select CEC_CORE if CEC_NOTIFIER
select SND_SOC_HDMI_CODEC if SND_SOC
help
  Support for NXP Semiconductors TDA998X HDMI encoders.
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c 
b/drivers/gpu/drm/i2c/tda998x_drv.c
index 16e0439cad44..eb9916bd84a4 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -16,8 +16,10 @@
  */
 
 #include 
+#include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -29,6 +31,8 @@
 #include 
 #include 
 
+#include 
+
 #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
 
 struct tda998x_audio_port {
@@ -55,6 +59,7 @@ struct tda998x_priv {
struct platform_device *audio_pdev;
struct mutex audio_mutex;
 
+   struct mutex edid_mutex;
wait_queue_head_t wq_edid;
volatile int wq_edid_wait;
 
@@ -67,6 +72,9 @@ struct tda998x_priv {
struct drm_connector connector;
 
struct tda998x_audio_port audio_port[2];
+   struct tda9950_glue cec_glue;
+   struct gpio_desc *calib;
+   struct cec_notifier *cec_notify;
 };
 
 #define conn_to_tda998x_priv(x) \
@@ -345,6 +353,12 @@ struct tda998x_priv {
 #define REG_CEC_INTSTATUS0xee/* read */
 # define CEC_INTSTATUS_CEC   (1 << 0)
 # define CEC_INTSTATUS_HDMI  (1 << 1)
+#define REG_CEC_CAL_XOSC_CTRL10xf2
+# define CEC_CAL_XOSC_CTRL1_ENA_CALBIT(0)
+#define REG_CEC_DES_FREQ2 0xf5
+# define CEC_DES_FREQ2_DIS_AUTOCAL BIT(7)
+#define REG_CEC_CLK   0xf6
+# define CEC_CLK_FRO  0x11
 #define REG_CEC_FRO_IM_CLK_CTRL   0xfb/* read/write */
 # define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
 # define CEC_FRO_IM_CLK_CTRL_ENA_OTP   (1 << 6)
@@ -359,6 +373,7 @@ struct tda998x_priv {
 # define CEC_RXSHPDLEV_HPD(1 << 1)
 
 #define REG_CEC_ENAMODS   0xff/* read/write */
+# define CEC_ENAMODS_EN_CEC_CLK   (1 << 7)
 # define CEC_ENAMODS_DIS_FRO  (1 << 6)
 # define CEC_ENAMODS_DIS_CCLK (1 << 5)
 # define CEC_ENAMODS_EN_RXSENS(1 << 2)
@@ -417,6 +432,114 @@ cec_read(struct tda998x_priv *priv, u8 addr)
return val;
 }
 
+static void cec_enamods(struct tda998x_priv *priv, u8 mods, bool enable)
+{
+   int val = cec_read(priv, REG_CEC_ENAMODS);
+
+   if (val < 0)
+   return;
+
+   if (enable)
+   val |= mods;
+   else
+   val &= ~mods;
+
+   cec_write(priv, REG_CEC_ENAMODS, val);
+}
+
+static void tda998x_cec_set_calibration(struct tda998x_priv *priv, bool enable)
+{
+   if (enable) {
+   u8 val;
+
+   cec_write(priv, 0xf3, 0xc0);
+   cec_write(priv, 0xf4, 0xd4);
+
+   /* Enable automatic calibration mode */
+   val = cec_read(priv, REG_CEC_DES_FREQ2);
+   val &= ~CEC_DES_FREQ2_DIS_AUTOCAL;
+   cec_write(priv, REG_CEC_DES_FREQ2, val);
+
+   /* Enable free running oscillator */
+   cec_write(priv, REG_CEC_CLK, CEC_CLK_FRO);
+   cec_enamods(priv, CEC_ENAMODS_DIS_FRO, false);
+
+   cec_write(priv, REG_CEC_CAL_XOSC_CTRL1,
+ CEC_CAL_XOSC_CTRL1_ENA_CAL);
+   } else {
+   cec_write(priv, REG_CEC_CAL_XOSC_CTRL1, 0);
+   }
+}
+
+/*
+ * Calibration for the internal oscillator: we need to set calibration mode,
+ * and then pulse the IRQ line low for a 10ms ± 1% period.
+ */
+static void tda998x_cec_calibration(struct tda998x_priv *priv)
+{
+   struct gpio_desc *calib = priv->calib;
+
+   mutex_lock(>edid_mutex);
+   if (priv->hdmi->irq > 0)
+   disable_irq(priv->hdmi->irq);
+   gpiod_direction_output(calib, 1);
+   tda998x_cec_set_calibration(priv, true);
+
+   local_irq_disable();
+   gpiod_set_value(calib, 0);
+   mdelay(10);
+   gpiod_set_value(calib, 1);
+   local_irq_enable();
+
+   tda998x_cec_set_calibration(priv, false);
+   gpiod_direction_input(calib);
+   if (priv->hdmi->irq > 0)
+   enable_irq(priv->hdmi->irq);
+   mutex_unlock(>edid_mutex);
+}
+
+static int tda998x_cec_hook_init(void *data)
+{
+   struct tda998x_priv *priv = data;
+   struct gpio_desc