On 04/21/2017 11:53 AM, Jose Abreu wrote: > 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@xxxxxxxxxxxx> > Cc: Carlos Palminha <palminha@xxxxxxxxxxxx> > Cc: Mauro Carvalho Chehab <mchehab@xxxxxxxxxx> > Cc: Hans Verkuil <hans.verkuil@xxxxxxxxx> > Cc: linux-kernel@xxxxxxxxxxxxxxx > Cc: linux-media@xxxxxxxxxxxxxxx > --- > 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 | 879 ++++++++++++++++++++++++++ > drivers/media/platform/dwc/dw-hdmi-phy-e405.h | 63 ++ > include/media/dwc/dw-hdmi-phy-pdata.h | 64 ++ > 7 files changed, 1019 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 ac026ee..5178229 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 63303d6..50bc148 100644 > --- a/drivers/media/platform/Makefile > +++ b/drivers/media/platform/Makefile > @@ -77,3 +77,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_VCODEC) += mtk-vcodec/ > obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/ > > obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/ > + > +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..dc00677 > --- /dev/null > +++ b/drivers/media/platform/dwc/dw-hdmi-phy-e405.c > @@ -0,0 +1,879 @@ > +/* > + * 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/delay.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/types.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@xxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Designware HDMI PHY e405 driver"); > +MODULE_LICENSE("Dual MIT/GPL"); > +MODULE_ALIAS("platform:" DW_PHY_E405_DRVNAME); > + > +#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; > + u16 mpll_status; > + unsigned char res; > + 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_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 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_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; > + u16 mpll_status; > + > + if (phy->version < 401) > + return ret; > + > + 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); > + udelay(1); > + phy_pddq(dw_dev, 0); > + mdelay(3); > + } > + } 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); > + udelay(1); > + phy_pddq(dw_dev, 0); > + mdelay(3); > + } > + > + return ret; > +} > + > +static void dw_phy_set_res(struct dw_phy_dev *dw_dev, unsigned char res) > +{ > + u16 val, res_idx; > + > + if (!dw_dev->phy_enabled) > + return; > + if (dw_dev->res == res) > + return; > + > + dev_dbg(dw_dev->dev, "%s: res=%d\n", __func__, res); > + > + 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; > + } > + > + /* Set phy in configuration mode */ > + phy_pddq(dw_dev, 1); > + > + /* Color depth */ > + val = phy_read(dw_dev, PHY_SYSTEM_CONFIG); > + val = (val & ~0x60) | (res_idx << 5); > + phy_write(dw_dev, val, PHY_SYSTEM_CONFIG); > + > + /* Enable phy */ > + phy_pddq(dw_dev, 0); > + > + dw_dev->res = res; > +} > + > +static void dw_phy_set_hdmi2(struct dw_phy_dev *dw_dev, bool on) > +{ > + u16 val; > + > + if (!dw_dev->phy_enabled) > + return; > + if (dw_dev->hdmi2 == on) > + return; > + > + dev_dbg(dw_dev->dev, "%s: on=%d\n", __func__, on); > + > + /* 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); > + > + dw_dev->hdmi2 = on; > +} > + > +static void dw_phy_set_scrambling(struct dw_phy_dev *dw_dev, bool on) > +{ > + u16 val; > + > + if (!dw_dev->phy_enabled) > + return; > + if (dw_dev->scrambling == on) > + return; > + > + dev_dbg(dw_dev->dev, "%s: on=%d\n", __func__, on); > + > + /* Set phy in configuration mode */ > + phy_pddq(dw_dev, 1); > + > + 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); > + > + /* Enable phy */ > + phy_pddq(dw_dev, 0); > + > + dw_dev->scrambling = on; > +} > + > +static int dw_phy_init(struct dw_phy_dev *dw_dev, unsigned char res, > + bool data_rate_6g, bool scrambling) > +{ > + 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, scrambling=%d\n", > + res, data_rate_6g, scrambling); > + > + 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); > + > + /* 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->res = res; > + dw_dev->hdmi2 = data_rate_6g; > + dw_dev->scrambling = scrambling; > + return 0; > +} > + > +static void dw_phy_enable(struct dw_phy_dev *dw_dev) > +{ > + if (dw_dev->phy_enabled) > + return; > + > + dw_phy_init(dw_dev, 8, false, false); > + phy_reset(dw_dev, 0); > + phy_pddq(dw_dev, 0); > + dw_dev->phy_enabled = true; > +} > + > +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_command *a = arg; > + 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: > + a->result = dw_phy_eq_init(dw_dev, a->nacq, a->force); > + 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: > + dw_phy_enable(dw_dev); > + dw_phy_set_res(dw_dev, a->res); set_color_depth would be more appropriate IMHO. > + dw_phy_set_hdmi2(dw_dev, a->hdmi2); > + dw_phy_set_scrambling(dw_dev, a->scrambling); > + a->result = 0; > + break; I'm not sure that you need SET_HDMI2, SET_SCRAMBLING and CONFIG: I think these can be deduced from the s_dv_timings op. I.e. the timings imply what should be set here. Note: I'm not sure whether the color depth can be deduced from the timings, I think that is not part of the timings. > + 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, > +}; > + > +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; > + > + dev_dbg(dev, "probe start\n"); > + > + /* 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; > + mutex_init(&dw_dev->lock); > + > + /* V4L2 initialization */ > + sd = &dw_dev->sd; > + v4l2_subdev_init(sd, &dw_phy_sd_ops); > + strlcpy(sd->name, dev_name(dev), sizeof(sd->name)); > + > + /* Phy initialization */ > + dw_phy_init(dw_dev, 8, false, false); > + > + /* All done */ > + dev_set_drvdata(dev, sd); > + dev_info(dev, "driver probed\n"); > + return 0; > +} > + > +static int dw_phy_remove(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + > + dev_info(dev, "driver removed\n"); > + return 0; > +} > + > +static struct platform_driver dw_phy_e405_driver = { > + .probe = dw_phy_probe, > + .remove = dw_phy_remove, > + .driver = { > + .name = DW_PHY_E405_DRVNAME, > + } > +}; > +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..c2962ec > --- /dev/null > +++ b/include/media/dwc/dw-hdmi-phy-pdata.h > @@ -0,0 +1,64 @@ > +/* > + * 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_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; > + u16 nacq; > + bool scrambling; > + bool force; > +}; > + > +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); This needs some comments. Esp. for 'pddq', 'svsmode' and the zcal ops. > +}; > + > +struct dw_phy_pdata { > + unsigned int version; > + unsigned int cfg_clk; > + const struct dw_phy_funcs *funcs; > + void *funcs_arg; > +}; > + > +#endif /* __DW_HDMI_PHY_PDATA_H__ */ > Regards, Hans