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__ */ >> + >> -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html