Re: [RFC] [media] Add Synopsys Designware HDMI RX PHY e405 driver

From: Jose Abreu
Date: Mon Nov 14 2016 - 11:46:45 EST


Hi Hans,



On 11-11-2016 14:52, Hans Verkuil wrote:
> Hi Jose,
>
> On 11/09/2016 06:43 PM, Jose Abreu wrote:
>> Hi All,
>>
>> This is a RFC patch for Synopsys Designware HDMI RX PHY e405.
>> This phy receives and decodes HDMI video that is delivered to
>> a controller. The controller bit is not yet ready for submission
>> but we are planning to submit it as soon as possible.
>>
>> Main included features in this driver are:
>> - Equalizer algorithm that chooses phy best settings
>> according to 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 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. For example, we can only set scrambling
>> when the sink detects the corresponding bit set in SCDC.
>>
>> Please notice that we plan to submit more phy drivers as they
>> are released, maintaining the newly created interface
>> (dw-phy-pdata.h) and using only one controller driver.
>>
>> I realize that this code needs a lot of polishment, specially
>> the equalizer part so I would really apreciate some feedback.
>>
>> Looking forward to your comments!
> I looked it over and I didn't see anything alarming :-)
>
> But it is hard to review without seeing the controller driver as well.
> When I can see how it is used by the controller driver then I can see
> if using ioctls here makes sense or not.
>
> Typically ioctls in subdevs are used for very device-specific actions.
> But perhaps what is happening here is required for all HDMI phys, and in
> that case new subdev ops could be added instead.
>
> Or we start with ioctls and later convert it to ops when it is clear that
> other phys need to do the same.
>
> Anyway, I think I'll have to wait until the controller is posted before I
> can do a proper review.
>
> Regards,
>
> Hans

Thanks for your answer! I am not sure about other controllers
phys but ours needs a special configuration when in HDMI 2.0
modes (like 4k@60Hz) and also a special bit set when in
scrambling mode so that the equalizing algorithm stabilizes
faster. Besides this it needs configuration parameters, that in
this case are passed by platform data.

I will then wait until I have the controller driver ready and
send a RFC with these two blocks.

Best regards,
Jose Miguel Abreu


>> Best regards,
>> Jose Miguel Abreu
>>
>> Signed-off-by: Jose Abreu <joabreu@xxxxxxxxxxxx>
>> Cc: Carlos Palminha <palminha@xxxxxxxxxxxx>
>> Cc: Mauro Carvalho Chehab <mchehab@xxxxxxxxxx>
>> Cc: linux-kernel@xxxxxxxxxxxxxxx
>> Cc: linux-media@xxxxxxxxxxxxxxx
>> ---
>> drivers/media/platform/Kconfig | 1 +
>> drivers/media/platform/Makefile | 2 +
>> drivers/media/platform/dw/Kconfig | 8 +
>> drivers/media/platform/dw/Makefile | 3 +
>> drivers/media/platform/dw/dw-phy-e405.c | 732 +++++++++++++++++++++++++++++++
>> drivers/media/platform/dw/dw-phy-e405.h | 48 ++
>> drivers/media/platform/dw/dw-phy-pdata.h | 47 ++
>> 7 files changed, 841 insertions(+)
>> create mode 100644 drivers/media/platform/dw/Kconfig
>> create mode 100644 drivers/media/platform/dw/Makefile
>> create mode 100644 drivers/media/platform/dw/dw-phy-e405.c
>> create mode 100644 drivers/media/platform/dw/dw-phy-e405.h
>> create mode 100644 drivers/media/platform/dw/dw-phy-pdata.h
>>
>> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
>> index 754edbf1..9e8e67f 100644
>> --- a/drivers/media/platform/Kconfig
>> +++ b/drivers/media/platform/Kconfig
>> @@ -120,6 +120,7 @@ source "drivers/media/platform/am437x/Kconfig"
>> source "drivers/media/platform/xilinx/Kconfig"
>> source "drivers/media/platform/rcar-vin/Kconfig"
>> source "drivers/media/platform/atmel/Kconfig"
>> +source "drivers/media/platform/dw/Kconfig"
>>
>> config VIDEO_TI_CAL
>> tristate "TI CAL (Camera Adaptation Layer) driver"
>> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
>> index f842933..fb2cf01 100644
>> --- a/drivers/media/platform/Makefile
>> +++ b/drivers/media/platform/Makefile
>> @@ -68,3 +68,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_VPU) += mtk-vpu/
>> obj-$(CONFIG_VIDEO_MEDIATEK_VCODEC) += mtk-vcodec/
>>
>> obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
>> +
>> +obj-y += dw/
>> diff --git a/drivers/media/platform/dw/Kconfig b/drivers/media/platform/dw/Kconfig
>> new file mode 100644
>> index 0000000..b3d7044
>> --- /dev/null
>> +++ b/drivers/media/platform/dw/Kconfig
>> @@ -0,0 +1,8 @@
>> +config VIDEO_DW_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-phy-e405.
>> diff --git a/drivers/media/platform/dw/Makefile b/drivers/media/platform/dw/Makefile
>> new file mode 100644
>> index 0000000..decc494
>> --- /dev/null
>> +++ b/drivers/media/platform/dw/Makefile
>> @@ -0,0 +1,3 @@
>> +# Makefile for Synopsys Designware HDMI RX
>> +
>> +obj-$(CONFIG_VIDEO_DW_PHY_E405) += dw-phy-e405.o
>> diff --git a/drivers/media/platform/dw/dw-phy-e405.c b/drivers/media/platform/dw/dw-phy-e405.c
>> new file mode 100644
>> index 0000000..e9c9cdf
>> --- /dev/null
>> +++ b/drivers/media/platform/dw/dw-phy-e405.c
>> @@ -0,0 +1,732 @@
>> +/*
>> + * Synopsys Designware HDMI RX PHY e405 driver
>> + *
>> + * Copyright (C) 2016 Synopsys, Inc.
>> + * Jose Abreu <joabreu@xxxxxxxxxxxx>
>> + *
>> + * This file is licensed under the terms of the GNU General Public
>> + * License version 2. This program is licensed "as is" without any
>> + * warranty of any kind, whether express or implied.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/types.h>
>> +#include <media/v4l2-subdev.h>
>> +#include "dw-phy-e405.h"
>> +#include "dw-phy-pdata.h"
>> +
>> +MODULE_AUTHOR("Jose Abreu <joabreu@xxxxxxxxxxxx>");
>> +MODULE_DESCRIPTION("Designware HDMI RX PHY e405 driver");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:dw-phy-e405");
>> +
>> +#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;
>> + bool phy_enabled;
>> + struct v4l2_subdev sd;
>> +};
>> +
>> +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;
>> + msleep(PHY_EQ_SLEEP_TIME_CDR);
>> + }
>> +
>> + if (i == PHY_EQ_WAIT_TIME_START) {
>> + dev_err(dw_dev->dev, "phy start conditions not achieved\n");
>> + return -ETIMEDOUT;
>> + }
>> +
>> + if (main_fsm_status & 0x400) {
>> + dev_err(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 void 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);
>> +
>> + msleep(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;
>> +
>> + dev_dbg(dw_dev->dev, "setting=%d: ch0.acq=%d, ch1.acq=%d, ch2.acq=%d\n",
>> + setting, ch0->acq, ch1->acq, ch2->acq);
>> +}
>> +
>> +static int dw_phy_eq_test_type(u16 setting, struct dw_phy_eq_ch *ch)
>> +{
>> + u16 step_slope = 0;
>> +
>> + if (ch->acq < ch->last_acq) {
>> + /* Long cable equalization */
>> + ch->acc += ch->last_acq - ch->acq;
>> + if ((!ch->valid_long_setting) && (ch->acq < 512) &&
>> + (ch->acc > 0)) {
>> + ch->best_long_setting = setting;
>> + ch->valid_long_setting = 1;
>> + }
>> +
>> + step_slope = ch->last_acq - ch->acq;
>> + }
>> +
>> + if (!ch->valid_short_setting) {
>> + /* 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) && (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 -EINVAL;
>> + }
>> +
>> + 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;
>> +
>> + dw_phy_eq_init_vars(ch0);
>> + dw_phy_eq_init_vars(ch1);
>> + dw_phy_eq_init_vars(ch2);
>> +
>> + dw_phy_eq_acquire_early_cnt(dw_dev, act, acq, ch0, ch1, ch2);
>> +
>> + while ((!ret_ch0) || (!ret_ch1) || (!ret_ch2)) {
>> + act++;
>> +
>> + ch0->last_acq = ch0->acq;
>> + ch1->last_acq = ch1->acq;
>> + ch2->last_acq = ch2->acq;
>> +
>> + dw_phy_eq_acquire_early_cnt(dw_dev, act, acq, ch0, ch1, ch2);
>> +
>> + if (!ret_ch0)
>> + ret_ch0 = dw_phy_eq_test_type(act, ch0);
>> + if (!ret_ch1)
>> + ret_ch1 = dw_phy_eq_test_type(act, ch1);
>> + if (!ret_ch2)
>> + ret_ch2 = dw_phy_eq_test_type(act, ch2);
>> + }
>> +
>> + if ((ret_ch0 < 0) || (ret_ch1 < 0) || (ret_ch2 < 0))
>> + 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)
>> +{
>> + struct dw_phy_pdata *phy = dw_dev->config;
>> + struct dw_phy_eq_ch ch0, ch1, ch2;
>> + u16 fat_bit_mask, lock_vector = 0x1;
>> + int min_max_length, i, ret = 0;
>> +
>> + if (phy->version < 401)
>> + return 0;
>> +
>> + 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 */
>> + return 0;
>> + dw_phy_eq_default(dw_dev);
>> + phy_pddq(dw_dev, 1);
>> + phy_pddq(dw_dev, 0);
>> + return ret;
>> + }
>> +
>> + 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_CH0_EQ_CTRL6);
>> + phy_write(dw_dev, fat_bit_mask, PHY_CH0_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, "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 void dw_phy_set_hdmi2(struct dw_phy_dev *dw_dev, bool on)
>> +{
>> + u16 val;
>> +
>> + /* Set phy in configuration mode */
>> + phy_pddq(dw_dev, 1);
>> +
>> + /* Operation for data rates between 3.4Gbps and 6Gbps */
>> + val = phy_read(dw_dev, PHY_CDR_CTRL_CNT);
>> + if (on)
>> + val |= BIT(8);
>> + else
>> + val &= ~BIT(8);
>> + phy_write(dw_dev, val, PHY_CDR_CTRL_CNT);
>> +
>> + /* Enable phy */
>> + phy_pddq(dw_dev, 0);
>> +}
>> +
>> +static void dw_phy_set_scrambling(struct dw_phy_dev *dw_dev, bool on)
>> +{
>> + u16 val;
>> +
>> + val = phy_read(dw_dev, PHY_OVL_PROT_CTRL);
>> + if (on)
>> + val |= GENMASK(7, 6);
>> + else
>> + val &= ~GENMASK(7, 6);
>> + phy_write(dw_dev, val, PHY_OVL_PROT_CTRL);
>> +}
>> +
>> +static int dw_phy_config(struct dw_phy_dev *dw_dev, unsigned char res,
>> + bool data_rate_6g)
>> +{
>> + struct device *dev = dw_dev->dev;
>> + struct dw_phy_pdata *phy = dw_dev->config;
>> + const struct dw_phy_mpll_config *mpll_cfg = dw_phy_e405_mpll_cfg;
>> + bool zcal_done;
>> + u16 val, res_idx;
>> + int timeout = 100;
>> +
>> + dev_dbg(dev, "configuring phy: res=%d, hdmi2=%d\n", res, data_rate_6g);
>> +
>> + switch (res) {
>> + 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 {
>> + udelay(1000);
>> + 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 |= (phy->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 (data_rate_6g)
>> + val |= BIT(8);
>> + else
>> + val &= ~BIT(8);
>> + phy_write(dw_dev, val, PHY_CDR_CTRL_CNT);
>> +
>> + /* Enable phy */
>> + 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);
>> + 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_command *a = arg;
>> +
>> + dev_dbg(dw_dev->dev, "%s: cmd=%d\n", __func__, cmd);
>> +
>> + switch (cmd) {
>> + case DW_PHY_IOCTL_EQ_INIT:
>> + a->result = dw_phy_eq_init(dw_dev, a->nacq);
>> + break;
>> + case DW_PHY_IOCTL_SET_HDMI2:
>> + dw_phy_set_hdmi2(dw_dev, a->hdmi2);
>> + a->result = 0;
>> + break;
>> + case DW_PHY_IOCTL_SET_SCRAMBLING:
>> + dw_phy_set_scrambling(dw_dev, a->scrambling);
>> + a->result = 0;
>> + break;
>> + case DW_PHY_IOCTL_CONFIG:
>> + a->result = dw_phy_config(dw_dev, a->res, a->hdmi2);
>> + dw_phy_set_scrambling(dw_dev, a->scrambling);
>> + break;
>> + default:
>> + return -ENOIOCTLCMD;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +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);
>> +
>> + if (!on)
>> + dw_phy_disable(dw_dev);
>> + 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,
>> +};
>> +
>> +static int dw_phy_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct dw_phy_dev *dw_dev;
>> + struct dw_phy_pdata *pdata = pdev->dev.platform_data;
>> + struct v4l2_subdev *sd;
>> +
>> + /* Resource allocation */
>> + dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
>> + if (!dw_dev)
>> + return -ENOMEM;
>> +
>> + /* Resource initialization */
>> + if (!pdata)
>> + return -EINVAL;
>> +
>> + dw_dev->dev = dev;
>> + dw_dev->config = pdata;
>> +
>> + /* V4L2 initialization */
>> + sd = &dw_dev->sd;
>> + v4l2_subdev_init(sd, &dw_phy_sd_ops);
>> + strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
>> +
>> + /* All done */
>> + dev_set_drvdata(dev, sd);
>> + return 0;
>> +}
>> +
>> +static int dw_phy_remove(struct platform_device *pdev)
>> +{
>> + return 0;
>> +}
>> +
>> +static struct platform_driver dw_phy_e405_driver = {
>> + .probe = dw_phy_probe,
>> + .remove = dw_phy_remove,
>> + .driver = {
>> + .name = "dw-phy-e405",
>> + }
>> +};
>> +module_platform_driver(dw_phy_e405_driver);
>> +
>> diff --git a/drivers/media/platform/dw/dw-phy-e405.h b/drivers/media/platform/dw/dw-phy-e405.h
>> new file mode 100644
>> index 0000000..a2a9057
>> --- /dev/null
>> +++ b/drivers/media/platform/dw/dw-phy-e405.h
>> @@ -0,0 +1,48 @@
>> +/*
>> + * Synopsys Designware HDMI RX PHY e405 driver
>> + *
>> + * Copyright (C) 2016 Synopsys, Inc.
>> + * Jose Abreu <joabreu@xxxxxxxxxxxx>
>> + *
>> + * This file is licensed under the terms of the GNU General Public
>> + * License version 2. This program is licensed "as is" without any
>> + * warranty of any kind, whether express or implied.
>> + */
>> +
>> +#ifndef __DW_PHY_E405_H__
>> +#define __DW_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_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_PHY_E405_H__ */
>> +
>> diff --git a/drivers/media/platform/dw/dw-phy-pdata.h b/drivers/media/platform/dw/dw-phy-pdata.h
>> new file mode 100644
>> index 0000000..b728a50
>> --- /dev/null
>> +++ b/drivers/media/platform/dw/dw-phy-pdata.h
>> @@ -0,0 +1,47 @@
>> +/*
>> + * Synopsys Designware HDMI RX PHY generic interface
>> + *
>> + * Copyright (C) 2016 Synopsys, Inc.
>> + * Jose Abreu <joabreu@xxxxxxxxxxxx>
>> + *
>> + * This file is licensed under the terms of the GNU General Public
>> + * License version 2. This program is licensed "as is" without any
>> + * warranty of any kind, whether express or implied.
>> + */
>> +
>> +#ifndef __DW_PHY_PDATA_H__
>> +#define __DW_PHY_PDATA_H__
>> +
>> +#define DW_PHY_IOCTL_EQ_INIT _IOW('R', 1, int)
>> +#define DW_PHY_IOCTL_SET_HDMI2 _IOW('R', 2, int)
>> +#define DW_PHY_IOCTL_SET_SCRAMBLING _IOW('R', 3, int)
>> +#define DW_PHY_IOCTL_CONFIG _IOW('R', 4, int)
>> +
>> +struct dw_phy_command {
>> + int result;
>> + unsigned char res;
>> + bool hdmi2;
>> + bool nacq;
>> + bool scrambling;
>> +};
>> +
>> +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 {
>> + unsigned int version;
>> + unsigned int cfg_clk;
>> + const struct dw_phy_funcs *funcs;
>> + void *funcs_arg;
>> +};
>> +
>> +#endif /* __DW_PHY_PDATA_H__ */
>> +
>>