diff mbox

[12/15] drm/sun4i: Add support for second clock parent to DW HDMI PHY clk driver

Message ID 20180519183127.2718-13-jernej.skrabec@siol.net (mailing list archive)
State New, archived
Headers show

Commit Message

Jernej Škrabec May 19, 2018, 6:31 p.m. UTC
Expand HDMI PHY clock driver to support second clock parent.

Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
---
 drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h      |  6 +-
 drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c     | 29 ++++++-
 drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c | 90 ++++++++++++++++------
 3 files changed, 98 insertions(+), 27 deletions(-)

Comments

kernel test robot May 21, 2018, 7:47 a.m. UTC | #1
Hi Jernej,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on drm/drm-next]
[also build test WARNING on v4.17-rc6 next-20180517]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Jernej-Skrabec/Add-support-for-R40-HDMI-pipeline/20180521-131839
base:   git://people.freedesktop.org/~airlied/linux.git drm-next
config: arm64-allmodconfig (attached as .config)
compiler: aarch64-linux-gnu-gcc (Debian 7.2.0-11) 7.2.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=arm64 

All warnings (new ones prefixed by >>):

   In file included from drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h:12:0,
                    from drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c:9:
   drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c: In function 'sun8i_hdmi_phy_config_h3':
>> drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c:191:7: warning: large integer implicitly truncated to unsigned type [-Woverflow]
          ~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
          ^
   include/linux/regmap.h:76:36: note: in definition of macro 'regmap_update_bits'
     regmap_update_bits_base(map, reg, mask, val, NULL, false, false)
                                       ^~~~

vim +191 drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c

     8	
   > 9	#include "sun8i_dw_hdmi.h"
    10	
    11	/*
    12	 * Address can be actually any value. Here is set to same value as
    13	 * it is set in BSP driver.
    14	 */
    15	#define I2C_ADDR	0x69
    16	
    17	static int sun8i_hdmi_phy_config_a83t(struct dw_hdmi *hdmi,
    18					      struct sun8i_hdmi_phy *phy,
    19					      unsigned int clk_rate)
    20	{
    21		regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
    22				   SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN,
    23				   SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN);
    24	
    25		/* power down */
    26		dw_hdmi_phy_gen2_txpwron(hdmi, 0);
    27		dw_hdmi_phy_gen2_pddq(hdmi, 1);
    28	
    29		dw_hdmi_phy_reset(hdmi);
    30	
    31		dw_hdmi_phy_gen2_pddq(hdmi, 0);
    32	
    33		dw_hdmi_phy_i2c_set_addr(hdmi, I2C_ADDR);
    34	
    35		/*
    36		 * Values are taken from BSP HDMI driver. Although AW didn't
    37		 * release any documentation, explanation of this values can
    38		 * be found in i.MX 6Dual/6Quad Reference Manual.
    39		 */
    40		if (clk_rate <= 27000000) {
    41			dw_hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06);
    42			dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
    43			dw_hdmi_phy_i2c_write(hdmi, 0x08da, 0x10);
    44			dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19);
    45			dw_hdmi_phy_i2c_write(hdmi, 0x0318, 0x0e);
    46			dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09);
    47		} else if (clk_rate <= 74250000) {
    48			dw_hdmi_phy_i2c_write(hdmi, 0x0540, 0x06);
    49			dw_hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
    50			dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10);
    51			dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19);
    52			dw_hdmi_phy_i2c_write(hdmi, 0x02b5, 0x0e);
    53			dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09);
    54		} else if (clk_rate <= 148500000) {
    55			dw_hdmi_phy_i2c_write(hdmi, 0x04a0, 0x06);
    56			dw_hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
    57			dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10);
    58			dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19);
    59			dw_hdmi_phy_i2c_write(hdmi, 0x0021, 0x0e);
    60			dw_hdmi_phy_i2c_write(hdmi, 0x8029, 0x09);
    61		} else {
    62			dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x06);
    63			dw_hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
    64			dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10);
    65			dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19);
    66			dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x0e);
    67			dw_hdmi_phy_i2c_write(hdmi, 0x802b, 0x09);
    68		}
    69	
    70		dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x1e);
    71		dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x13);
    72		dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x17);
    73	
    74		dw_hdmi_phy_gen2_txpwron(hdmi, 1);
    75	
    76		return 0;
    77	}
    78	
    79	static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
    80					    struct sun8i_hdmi_phy *phy,
    81					    unsigned int clk_rate)
    82	{
    83		u32 pll_cfg1_init;
    84		u32 pll_cfg2_init;
    85		u32 ana_cfg1_end;
    86		u32 ana_cfg2_init;
    87		u32 ana_cfg3_init;
    88		u32 b_offset = 0;
    89		u32 val;
    90	
    91		/* bandwidth / frequency independent settings */
    92	
    93		pll_cfg1_init = SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN |
    94				SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN |
    95				SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(7) |
    96				SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(1) |
    97				SUN8I_HDMI_PHY_PLL_CFG1_PLLDBEN |
    98				SUN8I_HDMI_PHY_PLL_CFG1_CS |
    99				SUN8I_HDMI_PHY_PLL_CFG1_CP_S(2) |
   100				SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63) |
   101				SUN8I_HDMI_PHY_PLL_CFG1_BWS;
   102	
   103		pll_cfg2_init = SUN8I_HDMI_PHY_PLL_CFG2_SV_H |
   104				SUN8I_HDMI_PHY_PLL_CFG2_VCOGAIN_EN |
   105				SUN8I_HDMI_PHY_PLL_CFG2_SDIV2;
   106	
   107		ana_cfg1_end = SUN8I_HDMI_PHY_ANA_CFG1_REG_SVBH(1) |
   108			       SUN8I_HDMI_PHY_ANA_CFG1_AMP_OPT |
   109			       SUN8I_HDMI_PHY_ANA_CFG1_EMP_OPT |
   110			       SUN8I_HDMI_PHY_ANA_CFG1_AMPCK_OPT |
   111			       SUN8I_HDMI_PHY_ANA_CFG1_EMPCK_OPT |
   112			       SUN8I_HDMI_PHY_ANA_CFG1_ENRCAL |
   113			       SUN8I_HDMI_PHY_ANA_CFG1_ENCALOG |
   114			       SUN8I_HDMI_PHY_ANA_CFG1_REG_SCKTMDS |
   115			       SUN8I_HDMI_PHY_ANA_CFG1_TMDSCLK_EN |
   116			       SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK |
   117			       SUN8I_HDMI_PHY_ANA_CFG1_TXEN_ALL |
   118			       SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDSCLK |
   119			       SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS2 |
   120			       SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS1 |
   121			       SUN8I_HDMI_PHY_ANA_CFG1_BIASEN_TMDS0 |
   122			       SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS2 |
   123			       SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS1 |
   124			       SUN8I_HDMI_PHY_ANA_CFG1_ENP2S_TMDS0 |
   125			       SUN8I_HDMI_PHY_ANA_CFG1_CKEN |
   126			       SUN8I_HDMI_PHY_ANA_CFG1_LDOEN |
   127			       SUN8I_HDMI_PHY_ANA_CFG1_ENVBS |
   128			       SUN8I_HDMI_PHY_ANA_CFG1_ENBI;
   129	
   130		ana_cfg2_init = SUN8I_HDMI_PHY_ANA_CFG2_M_EN |
   131				SUN8I_HDMI_PHY_ANA_CFG2_REG_DENCK |
   132				SUN8I_HDMI_PHY_ANA_CFG2_REG_DEN |
   133				SUN8I_HDMI_PHY_ANA_CFG2_REG_CKSS(1) |
   134				SUN8I_HDMI_PHY_ANA_CFG2_REG_CSMPS(1);
   135	
   136		ana_cfg3_init = SUN8I_HDMI_PHY_ANA_CFG3_REG_WIRE(0x3e0) |
   137				SUN8I_HDMI_PHY_ANA_CFG3_SDAEN |
   138				SUN8I_HDMI_PHY_ANA_CFG3_SCLEN;
   139	
   140		/* bandwidth / frequency dependent settings */
   141		if (clk_rate <= 27000000) {
   142			pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
   143					 SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
   144			pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
   145					 SUN8I_HDMI_PHY_PLL_CFG2_S(4);
   146			ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW;
   147			ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) |
   148					 SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal);
   149			ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(3) |
   150					 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(5);
   151		} else if (clk_rate <= 74250000) {
   152			pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
   153					 SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
   154			pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
   155					 SUN8I_HDMI_PHY_PLL_CFG2_S(5);
   156			ana_cfg1_end |= SUN8I_HDMI_PHY_ANA_CFG1_REG_CALSW;
   157			ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4) |
   158					 SUN8I_HDMI_PHY_ANA_CFG2_REG_RESDI(phy->rcal);
   159			ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(5) |
   160					 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(7);
   161		} else if (clk_rate <= 148500000) {
   162			pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33 |
   163					 SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(32);
   164			pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(4) |
   165					 SUN8I_HDMI_PHY_PLL_CFG2_S(6);
   166			ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK |
   167					 SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW |
   168					 SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(2);
   169			ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(7) |
   170					 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(9);
   171		} else {
   172			b_offset = 2;
   173			pll_cfg1_init |= SUN8I_HDMI_PHY_PLL_CFG1_CNT_INT(63);
   174			pll_cfg2_init |= SUN8I_HDMI_PHY_PLL_CFG2_VCO_S(6) |
   175					 SUN8I_HDMI_PHY_PLL_CFG2_S(7);
   176			ana_cfg2_init |= SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSWCK |
   177					 SUN8I_HDMI_PHY_ANA_CFG2_REG_BIGSW |
   178					 SUN8I_HDMI_PHY_ANA_CFG2_REG_SLV(4);
   179			ana_cfg3_init |= SUN8I_HDMI_PHY_ANA_CFG3_REG_AMPCK(9) |
   180					 SUN8I_HDMI_PHY_ANA_CFG3_REG_AMP(13);
   181		}
   182	
   183		regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
   184				   SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
   185	
   186		/*
   187		 * NOTE: We have to be careful not to overwrite PHY parent
   188		 * clock selection bit and clock divider.
   189		 */
   190		regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
 > 191				   ~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
   192				   pll_cfg1_init);
   193		regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
   194				   (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
   195				   pll_cfg2_init);
   196		usleep_range(10000, 15000);
   197		regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG3_REG,
   198			     SUN8I_HDMI_PHY_PLL_CFG3_SOUT_DIV2);
   199		regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
   200				   SUN8I_HDMI_PHY_PLL_CFG1_PLLEN,
   201				   SUN8I_HDMI_PHY_PLL_CFG1_PLLEN);
   202		msleep(100);
   203	
   204		/* get B value */
   205		regmap_read(phy->regs, SUN8I_HDMI_PHY_ANA_STS_REG, &val);
   206		val = (val & SUN8I_HDMI_PHY_ANA_STS_B_OUT_MSK) >>
   207			SUN8I_HDMI_PHY_ANA_STS_B_OUT_SHIFT;
   208		val = min(val + b_offset, (u32)0x3f);
   209	
   210		regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
   211				   SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 |
   212				   SUN8I_HDMI_PHY_PLL_CFG1_REG_OD,
   213				   SUN8I_HDMI_PHY_PLL_CFG1_REG_OD1 |
   214				   SUN8I_HDMI_PHY_PLL_CFG1_REG_OD);
   215		regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
   216				   SUN8I_HDMI_PHY_PLL_CFG1_B_IN_MSK,
   217				   val << SUN8I_HDMI_PHY_PLL_CFG1_B_IN_SHIFT);
   218		msleep(100);
   219		regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG, ana_cfg1_end);
   220		regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG2_REG, ana_cfg2_init);
   221		regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG3_REG, ana_cfg3_init);
   222	
   223		return 0;
   224	}
   225	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Maxime Ripard May 21, 2018, 8:12 a.m. UTC | #2
On Sat, May 19, 2018 at 08:31:24PM +0200, Jernej Skrabec wrote:
> Expand HDMI PHY clock driver to support second clock parent.
> 
> Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> ---
>  drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h      |  6 +-
>  drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c     | 29 ++++++-
>  drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c | 90 ++++++++++++++++------
>  3 files changed, 98 insertions(+), 27 deletions(-)
> 
> diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> index 801a17222762..aadbe0a10b0c 100644
> --- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> @@ -98,7 +98,8 @@
>  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN		BIT(29)
>  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN		BIT(28)
>  #define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33	BIT(27)
> -#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL	BIT(26)
> +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK	BIT(26)
> +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT	26
>  #define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN		BIT(25)
>  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x)	((x) << 22)
>  #define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x)	((x) << 20)
> @@ -190,6 +191,7 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi);
>  void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
>  const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
>  
> -int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
> +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
> +			 bool second_parent);
>  
>  #endif /* _SUN8I_DW_HDMI_H_ */
> diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> index deba47ed69d8..7a911f0a3ae3 100644
> --- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> @@ -183,7 +183,13 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
>  	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
>  			   SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
>  
> -	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
> +	/*
> +	 * NOTE: We have to be careful not to overwrite PHY parent
> +	 * clock selection bit and clock divider.
> +	 */
> +	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
> +			   ~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
> +			   pll_cfg1_init);

It feels like it belongs in a separate patch.

>  	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
>  			   (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
>  			   pll_cfg2_init);
> @@ -352,6 +358,10 @@ static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy)
>  			   SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
>  			   SUN8I_HDMI_PHY_ANA_CFG3_SDAEN);
>  
> +	/* reset PLL clock configuration */
> +	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0);
> +	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, 0);
> +

Ditto,

> +
> +		/*
> +		 * Even though HDMI PHY clock doesn't have enable/disable
> +		 * handlers, we have to enable it. Otherwise it could happen
> +		 * that parent PLL is not enabled by clock framework in a
> +		 * highly unlikely event when parent PLL is used solely for
> +		 * HDMI PHY clock.
> +		 */
> +		clk_prepare_enable(phy->clk_phy);

The implementation of the clock doesn't really matter in our API
usage. If we're using a clock, we have to call
clk_prepare_enable. That's documented everywhere, and mentionning how
the clock is implemented is an abstraction leakage and it's
irrelevant. I'd simply remove the comment here.

And it should be in a separate patch as well.

Maxime
Jernej Škrabec May 21, 2018, 3:02 p.m. UTC | #3
Hi,

Dne ponedeljek, 21. maj 2018 ob 10:12:53 CEST je Maxime Ripard napisal(a):
> On Sat, May 19, 2018 at 08:31:24PM +0200, Jernej Skrabec wrote:
> > Expand HDMI PHY clock driver to support second clock parent.
> > 
> > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> > ---
> > 
> >  drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h      |  6 +-
> >  drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c     | 29 ++++++-
> >  drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c | 90 ++++++++++++++++------
> >  3 files changed, 98 insertions(+), 27 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> > b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h index 801a17222762..aadbe0a10b0c
> > 100644
> > --- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> > +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> > @@ -98,7 +98,8 @@
> > 
> >  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN		BIT(29)
> >  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN		BIT(28)
> >  #define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33	BIT(27)
> > 
> > -#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL	BIT(26)
> > +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK	BIT(26)
> > +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT	26
> > 
> >  #define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN		BIT(25)
> >  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x)	((x) << 22)
> >  #define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x)	((x) << 20)
> > 
> > @@ -190,6 +191,7 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi
> > *hdmi);
> > 
> >  void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
> >  const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
> > 
> > -int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
> > +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
> > +			 bool second_parent);
> > 
> >  #endif /* _SUN8I_DW_HDMI_H_ */
> > 
> > diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> > b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c index deba47ed69d8..7a911f0a3ae3
> > 100644
> > --- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> > +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> > @@ -183,7 +183,13 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi
> > *hdmi,> 
> >  	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
> >  	
> >  			   SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
> > 
> > -	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
> > +	/*
> > +	 * NOTE: We have to be careful not to overwrite PHY parent
> > +	 * clock selection bit and clock divider.
> > +	 */
> > +	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
> > +			   ~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
> > +			   pll_cfg1_init);
> 
> It feels like it belongs in a separate patch.

Why? clk_set_rate() on HDMI PHY clock is called before this function, so 
SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL bit is already set to correct value. Original 
code doesn't take this into account and sets it to 0 here in all cases, which 
obviously is not right.

If you insist on splitting this in new patch, it has to be applied before 
clock changes. It would also need to include "reset PLL clock configuration" 
chunk (next change in this patch), otherwise other SoCs with only one parent 
could get 1 there, which is obviously wrong. Note that I didn't really tested 
if default value of this bit is 0 or 1, but I wouldn't really like to depend 
on default values.

> 
> >  	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
> >  	
> >  			   (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
> >  			   pll_cfg2_init);
> > 
> > @@ -352,6 +358,10 @@ static void sun8i_hdmi_phy_init_h3(struct
> > sun8i_hdmi_phy *phy)> 
> >  			   SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
> >  			   SUN8I_HDMI_PHY_ANA_CFG3_SDAEN);
> > 
> > +	/* reset PLL clock configuration */
> > +	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0);
> > +	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, 0);
> > +
> 
> Ditto,
> 
> > +
> > +		/*
> > +		 * Even though HDMI PHY clock doesn't have enable/disable
> > +		 * handlers, we have to enable it. Otherwise it could happen
> > +		 * that parent PLL is not enabled by clock framework in a
> > +		 * highly unlikely event when parent PLL is used solely for
> > +		 * HDMI PHY clock.
> > +		 */
> > +		clk_prepare_enable(phy->clk_phy);
> 
> The implementation of the clock doesn't really matter in our API
> usage. If we're using a clock, we have to call
> clk_prepare_enable. That's documented everywhere, and mentionning how
> the clock is implemented is an abstraction leakage and it's
> irrelevant. I'd simply remove the comment here.
> 
> And it should be in a separate patch as well.

OK. Should I add Fixes tag, since current code obviously is not by the specs? 
It could still get in 4.17...

Best regards,
Jernej
Maxime Ripard May 24, 2018, 8:27 a.m. UTC | #4
On Mon, May 21, 2018 at 05:02:23PM +0200, Jernej Škrabec wrote:
> Hi,
> 
> Dne ponedeljek, 21. maj 2018 ob 10:12:53 CEST je Maxime Ripard napisal(a):
> > On Sat, May 19, 2018 at 08:31:24PM +0200, Jernej Skrabec wrote:
> > > Expand HDMI PHY clock driver to support second clock parent.
> > > 
> > > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net>
> > > ---
> > > 
> > >  drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h      |  6 +-
> > >  drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c     | 29 ++++++-
> > >  drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c | 90 ++++++++++++++++------
> > >  3 files changed, 98 insertions(+), 27 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> > > b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h index 801a17222762..aadbe0a10b0c
> > > 100644
> > > --- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> > > +++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
> > > @@ -98,7 +98,8 @@
> > > 
> > >  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN		BIT(29)
> > >  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN		BIT(28)
> > >  #define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33	BIT(27)
> > > 
> > > -#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL	BIT(26)
> > > +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK	BIT(26)
> > > +#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT	26
> > > 
> > >  #define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN		BIT(25)
> > >  #define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x)	((x) << 22)
> > >  #define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x)	((x) << 20)
> > > 
> > > @@ -190,6 +191,7 @@ void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi
> > > *hdmi);
> > > 
> > >  void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
> > >  const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
> > > 
> > > -int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
> > > +int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
> > > +			 bool second_parent);
> > > 
> > >  #endif /* _SUN8I_DW_HDMI_H_ */
> > > 
> > > diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> > > b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c index deba47ed69d8..7a911f0a3ae3
> > > 100644
> > > --- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> > > +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
> > > @@ -183,7 +183,13 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi
> > > *hdmi,> 
> > >  	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
> > >  	
> > >  			   SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
> > > 
> > > -	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
> > > +	/*
> > > +	 * NOTE: We have to be careful not to overwrite PHY parent
> > > +	 * clock selection bit and clock divider.
> > > +	 */
> > > +	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
> > > +			   ~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
> > > +			   pll_cfg1_init);
> > 
> > It feels like it belongs in a separate patch.
> 
> Why? clk_set_rate() on HDMI PHY clock is called before this function, so 
> SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL bit is already set to correct value. Original 
> code doesn't take this into account and sets it to 0 here in all cases, which 
> obviously is not right.
> 
> If you insist on splitting this in new patch, it has to be applied before 
> clock changes. It would also need to include "reset PLL clock configuration" 
> chunk (next change in this patch), otherwise other SoCs with only one parent 
> could get 1 there, which is obviously wrong. Note that I didn't really tested 
> if default value of this bit is 0 or 1, but I wouldn't really like to depend 
> on default values.

I don't have a strong feeling about this, but to me, the fact that you
don't want to overwrite the muxing bit because the clock might use it
is sensible in itself, disregarding the fact that the clock *will* use
it.

> 
> > 
> > >  	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
> > >  	
> > >  			   (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
> > >  			   pll_cfg2_init);
> > > 
> > > @@ -352,6 +358,10 @@ static void sun8i_hdmi_phy_init_h3(struct
> > > sun8i_hdmi_phy *phy)> 
> > >  			   SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
> > >  			   SUN8I_HDMI_PHY_ANA_CFG3_SDAEN);
> > > 
> > > +	/* reset PLL clock configuration */
> > > +	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0);
> > > +	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, 0);
> > > +
> > 
> > Ditto,
> > 
> > > +
> > > +		/*
> > > +		 * Even though HDMI PHY clock doesn't have enable/disable
> > > +		 * handlers, we have to enable it. Otherwise it could happen
> > > +		 * that parent PLL is not enabled by clock framework in a
> > > +		 * highly unlikely event when parent PLL is used solely for
> > > +		 * HDMI PHY clock.
> > > +		 */
> > > +		clk_prepare_enable(phy->clk_phy);
> > 
> > The implementation of the clock doesn't really matter in our API
> > usage. If we're using a clock, we have to call
> > clk_prepare_enable. That's documented everywhere, and mentionning how
> > the clock is implemented is an abstraction leakage and it's
> > irrelevant. I'd simply remove the comment here.
> > 
> > And it should be in a separate patch as well.
> 
> OK. Should I add Fixes tag, since current code obviously is not by the specs? 
> It could still get in 4.17...

Yep!

Maxime
diff mbox

Patch

diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
index 801a17222762..aadbe0a10b0c 100644
--- a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
@@ -98,7 +98,8 @@ 
 #define SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN		BIT(29)
 #define SUN8I_HDMI_PHY_PLL_CFG1_LDO1_EN		BIT(28)
 #define SUN8I_HDMI_PHY_PLL_CFG1_HV_IS_33	BIT(27)
-#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL	BIT(26)
+#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK	BIT(26)
+#define SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT	26
 #define SUN8I_HDMI_PHY_PLL_CFG1_PLLEN		BIT(25)
 #define SUN8I_HDMI_PHY_PLL_CFG1_LDO_VSET(x)	((x) << 22)
 #define SUN8I_HDMI_PHY_PLL_CFG1_UNKNOWN(x)	((x) << 20)
@@ -190,6 +191,7 @@  void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi);
 void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
 const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
 
-int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev);
+int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
+			 bool second_parent);
 
 #endif /* _SUN8I_DW_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
index deba47ed69d8..7a911f0a3ae3 100644
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
@@ -183,7 +183,13 @@  static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
 	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
 			   SUN8I_HDMI_PHY_ANA_CFG1_TXEN_MASK, 0);
 
-	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, pll_cfg1_init);
+	/*
+	 * NOTE: We have to be careful not to overwrite PHY parent
+	 * clock selection bit and clock divider.
+	 */
+	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
+			   ~SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
+			   pll_cfg1_init);
 	regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG,
 			   (u32)~SUN8I_HDMI_PHY_PLL_CFG2_PREDIV_MSK,
 			   pll_cfg2_init);
@@ -352,6 +358,10 @@  static void sun8i_hdmi_phy_init_h3(struct sun8i_hdmi_phy *phy)
 			   SUN8I_HDMI_PHY_ANA_CFG3_SCLEN |
 			   SUN8I_HDMI_PHY_ANA_CFG3_SDAEN);
 
+	/* reset PLL clock configuration */
+	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0);
+	regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG2_REG, 0);
+
 	/* set HW control of CEC pins */
 	regmap_write(phy->regs, SUN8I_HDMI_PHY_CEC_REG, 0);
 
@@ -481,18 +491,28 @@  int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
 			}
 		}
 
-		ret = sun8i_phy_clk_create(phy, dev);
+		ret = sun8i_phy_clk_create(phy, dev,
+					   phy->variant->has_second_pll);
 		if (ret) {
 			dev_err(dev, "Couldn't create the PHY clock\n");
 			goto err_put_clk_pll1;
 		}
+
+		/*
+		 * Even though HDMI PHY clock doesn't have enable/disable
+		 * handlers, we have to enable it. Otherwise it could happen
+		 * that parent PLL is not enabled by clock framework in a
+		 * highly unlikely event when parent PLL is used solely for
+		 * HDMI PHY clock.
+		 */
+		clk_prepare_enable(phy->clk_phy);
 	}
 
 	phy->rst_phy = of_reset_control_get_shared(node, "phy");
 	if (IS_ERR(phy->rst_phy)) {
 		dev_err(dev, "Could not get phy reset control\n");
 		ret = PTR_ERR(phy->rst_phy);
-		goto err_put_clk_pll1;
+		goto err_disable_clk_phy;
 	}
 
 	ret = reset_control_deassert(phy->rst_phy);
@@ -523,6 +543,8 @@  int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
 	reset_control_assert(phy->rst_phy);
 err_put_rst_phy:
 	reset_control_put(phy->rst_phy);
+err_disable_clk_phy:
+	clk_disable_unprepare(phy->clk_phy);
 err_put_clk_pll1:
 	clk_put(phy->clk_pll1);
 err_put_clk_pll0:
@@ -541,6 +563,7 @@  void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi)
 
 	clk_disable_unprepare(phy->clk_mod);
 	clk_disable_unprepare(phy->clk_bus);
+	clk_disable_unprepare(phy->clk_phy);
 
 	reset_control_assert(phy->rst_phy);
 
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
index faea449812f8..a4d31fe3abff 100644
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy_clk.c
@@ -22,35 +22,45 @@  static int sun8i_phy_clk_determine_rate(struct clk_hw *hw,
 {
 	unsigned long rate = req->rate;
 	unsigned long best_rate = 0;
+	struct clk_hw *best_parent = NULL;
 	struct clk_hw *parent;
 	int best_div = 1;
-	int i;
+	int i, p;
 
-	parent = clk_hw_get_parent(hw);
-
-	for (i = 1; i <= 16; i++) {
-		unsigned long ideal = rate * i;
-		unsigned long rounded;
-
-		rounded = clk_hw_round_rate(parent, ideal);
+	for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
+		parent = clk_hw_get_parent_by_index(hw, p);
+		if (!parent)
+			continue;
 
-		if (rounded == ideal) {
-			best_rate = rounded;
-			best_div = i;
-			break;
+		for (i = 1; i <= 16; i++) {
+			unsigned long ideal = rate * i;
+			unsigned long rounded;
+
+			rounded = clk_hw_round_rate(parent, ideal);
+
+			if (rounded == ideal) {
+				best_rate = rounded;
+				best_div = i;
+				best_parent = parent;
+				break;
+			}
+
+			if (!best_rate ||
+			    abs(rate - rounded / i) <
+			    abs(rate - best_rate / best_div)) {
+				best_rate = rounded;
+				best_div = i;
+				best_parent = parent;
+			}
 		}
 
-		if (!best_rate ||
-		    abs(rate - rounded / i) <
-		    abs(rate - best_rate / best_div)) {
-			best_rate = rounded;
-			best_div = i;
-		}
+		if (best_rate / best_div == rate)
+			break;
 	}
 
 	req->rate = best_rate / best_div;
 	req->best_parent_rate = best_rate;
-	req->best_parent_hw = parent;
+	req->best_parent_hw = best_parent;
 
 	return 0;
 }
@@ -95,22 +105,58 @@  static int sun8i_phy_clk_set_rate(struct clk_hw *hw, unsigned long rate,
 	return 0;
 }
 
+static u8 sun8i_phy_clk_get_parent(struct clk_hw *hw)
+{
+	struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
+	u32 reg;
+
+	regmap_read(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, &reg);
+	reg = (reg & SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK) >>
+	      SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT;
+
+	return reg;
+}
+
+static int sun8i_phy_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct sun8i_phy_clk *priv = hw_to_phy_clk(hw);
+
+	if (index > 1)
+		return -EINVAL;
+
+	regmap_update_bits(priv->phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG,
+			   SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_MSK,
+			   index << SUN8I_HDMI_PHY_PLL_CFG1_CKIN_SEL_SHIFT);
+
+	return 0;
+}
+
 static const struct clk_ops sun8i_phy_clk_ops = {
 	.determine_rate	= sun8i_phy_clk_determine_rate,
 	.recalc_rate	= sun8i_phy_clk_recalc_rate,
 	.set_rate	= sun8i_phy_clk_set_rate,
+
+	.get_parent	= sun8i_phy_clk_get_parent,
+	.set_parent	= sun8i_phy_clk_set_parent,
 };
 
-int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
+int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev,
+			 bool second_parent)
 {
 	struct clk_init_data init;
 	struct sun8i_phy_clk *priv;
-	const char *parents[1];
+	const char *parents[2];
 
 	parents[0] = __clk_get_name(phy->clk_pll0);
 	if (!parents[0])
 		return -ENODEV;
 
+	if (second_parent) {
+		parents[1] = __clk_get_name(phy->clk_pll1);
+		if (!parents[1])
+			return -ENODEV;
+	}
+
 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
 		return -ENOMEM;
@@ -118,7 +164,7 @@  int sun8i_phy_clk_create(struct sun8i_hdmi_phy *phy, struct device *dev)
 	init.name = "hdmi-phy-clk";
 	init.ops = &sun8i_phy_clk_ops;
 	init.parent_names = parents;
-	init.num_parents = 1;
+	init.num_parents = second_parent ? 2 : 1;
 	init.flags = CLK_SET_RATE_PARENT;
 
 	priv->phy = phy;