Re: [PATCH 08/12] media: ov5640: Adjust the clock based on the expected rate

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Maxime,

On Fri, Mar 02, 2018 at 03:34:56PM +0100, Maxime Ripard wrote:
...
> @@ -902,6 +920,246 @@ static int ov5640_mod_reg(struct ov5640_dev *sensor, u16 reg,
>  	return ov5640_write_reg(sensor, reg, val);
>  }
>  
> +/*
> + * After spending way too much time trying the various combinations, I
> + * believe the clock tree is as follows:

Wow! I've never heard of anyone doing this on non-SMIA compliant sensors.

> + *
> + *   +--------------+
> + *   |  Oscillator  |

I wonder if this should be simply called external clock, that's what the
sensor uses.

> + *   +------+-------+
> + *          |
> + *   +------+-------+
> + *   | System clock | - reg 0x3035, bits 4-7
> + *   +------+-------+
> + *          |
> + *   +------+-------+ - reg 0x3036, for the multiplier
> + *   |     PLL      | - reg 0x3037, bits 4 for the root divider
> + *   +------+-------+ - reg 0x3037, bits 0-3 for the pre-divider
> + *          |
> + *   +------+-------+
> + *   |     SCLK     | - reg 0x3108, bits 0-1 for the root divider
> + *   +------+-------+
> + *          |
> + *   +------+-------+
> + *   |    PCLK      | - reg 0x3108, bits 4-5 for the root divider
> + *   +--------------+
> + *
> + * This is deviating from the datasheet at least for the register
> + * 0x3108, since it's said here that the PCLK would be clocked from
> + * the PLL. However, changing the SCLK divider value has a direct
> + * effect on the PCLK rate, which wouldn't be the case if both PCLK
> + * and SCLK were to be sourced from the PLL.
> + *
> + * These parameters also match perfectly the rate that is output by
> + * the sensor, so we shouldn't have too much factors missing (or they
> + * would be set to 1).
> + */
> +
> +/*
> + * FIXME: This is supposed to be ranging from 1 to 16, but the value
> + * is always set to either 1 or 2 in the vendor kernels.

There could be limits for the clock rates after the first divider. In
practice the clock rates are mostly one of the common frequencies (9,6; 12;
19,2 or 24 MHz) so there's no need to use the other values.

> + */
> +#define OV5640_SYSDIV_MIN	1
> +#define OV5640_SYSDIV_MAX	2
> +
> +static unsigned long ov5640_calc_sysclk(struct ov5640_dev *sensor,
> +					unsigned long rate,
> +					u8 *sysdiv)
> +{
> +	unsigned long best = ~0;
> +	u8 best_sysdiv = 1;
> +	u8 _sysdiv;
> +
> +	for (_sysdiv = OV5640_SYSDIV_MIN;
> +	     _sysdiv <= OV5640_SYSDIV_MAX;
> +	     _sysdiv++) {
> +		unsigned long tmp;
> +
> +		tmp = sensor->xclk_freq / _sysdiv;
> +		if (abs(rate - tmp) < abs(rate - best)) {
> +			best = tmp;
> +			best_sysdiv = _sysdiv;
> +		}
> +
> +		if (tmp == rate)
> +			goto out;
> +	}
> +
> +out:
> +	*sysdiv = best_sysdiv;
> +	return best;
> +}
> +
> +/*
> + * FIXME: This is supposed to be ranging from 1 to 8, but the value is
> + * always set to 3 in the vendor kernels.
> + */
> +#define OV5640_PLL_PREDIV_MIN	3
> +#define OV5640_PLL_PREDIV_MAX	3


Same reasoning here than above. I might leave a comment documenting the
values the hardware supports, removing FIXME as this isn't really an issue
as far as I see.

> +
> +/*
> + * FIXME: This is supposed to be ranging from 1 to 2, but the value is
> + * always set to 1 in the vendor kernels.
> + */
> +#define OV5640_PLL_ROOT_DIV_MIN	1
> +#define OV5640_PLL_ROOT_DIV_MAX	1
> +
> +#define OV5640_PLL_MULT_MIN	4
> +#define OV5640_PLL_MULT_MAX	252
> +
> +static unsigned long ov5640_calc_pll(struct ov5640_dev *sensor,
> +				     unsigned long rate,
> +				     u8 *sysdiv, u8 *prediv, u8 *rdiv, u8 *mult)
> +{
> +	unsigned long best = ~0;
> +	u8 best_sysdiv = 1, best_prediv = 1, best_mult = 1, best_rdiv = 1;
> +	u8 _prediv, _mult, _rdiv;
> +
> +	for (_prediv = OV5640_PLL_PREDIV_MIN;
> +	     _prediv <= OV5640_PLL_PREDIV_MAX;
> +	     _prediv++) {
> +		for (_mult = OV5640_PLL_MULT_MIN;
> +		     _mult <= OV5640_PLL_MULT_MAX;
> +		     _mult++) {
> +			for (_rdiv = OV5640_PLL_ROOT_DIV_MIN;
> +			     _rdiv <= OV5640_PLL_ROOT_DIV_MAX;
> +			     _rdiv++) {
> +				unsigned long pll;
> +				unsigned long sysclk;
> +				u8 _sysdiv;
> +
> +				/*
> +				 * The PLL multiplier cannot be odd if
> +				 * above 127.
> +				 */
> +				if (_mult > 127 && !(_mult % 2))
> +					continue;
> +
> +				sysclk = rate * _prediv * _rdiv / _mult;
> +				sysclk = ov5640_calc_sysclk(sensor, sysclk,
> +							    &_sysdiv);
> +
> +				pll = sysclk / _rdiv / _prediv * _mult;
> +				if (abs(rate - pll) < abs(rate - best)) {
> +					best = pll;
> +					best_sysdiv = _sysdiv;
> +					best_prediv = _prediv;
> +					best_mult = _mult;
> +					best_rdiv = _rdiv;

The smiapp PLL calculator only accepts an exact match. That hasn't been an
issue previously, I wonder if this would work here, too.

I think you could remove the inner loop, too, if put what
ov5640_calc_sysclk() does here. You know the desired clock rate so you can
calculate the total divisor (_rdiv * sysclk divider).

> +				}
> +
> +				if (pll == rate)
> +					goto out;
> +			}
> +		}
> +	}
> +
> +out:
> +	*sysdiv = best_sysdiv;
> +	*prediv = best_prediv;
> +	*mult = best_mult;
> +	*rdiv = best_rdiv;
> +
> +	return best;
> +}
> +
> +/*
> + * FIXME: This is supposed to be ranging from 1 to 8, but the value is
> + * always set to 1 in the vendor kernels.
> + */
> +#define OV5640_PCLK_ROOT_DIV_MIN	1
> +#define OV5640_PCLK_ROOT_DIV_MAX	1
> +
> +static unsigned long ov5640_calc_pclk(struct ov5640_dev *sensor,
> +				      unsigned long rate,
> +				      u8 *sysdiv, u8 *prediv, u8 *pll_rdiv,
> +				      u8 *mult, u8 *pclk_rdiv)
> +{
> +	unsigned long best = ~0;
> +	u8 best_sysdiv = 1, best_prediv = 1, best_mult = 1, best_pll_rdiv = 1;
> +	u8 best_pclk_rdiv = 1;
> +	u8 _pclk_rdiv;
> +
> +	for (_pclk_rdiv = OV5640_PCLK_ROOT_DIV_MIN;
> +	     _pclk_rdiv <= OV5640_PCLK_ROOT_DIV_MAX;
> +	     _pclk_rdiv <<= 1) {
> +		unsigned long pll, pclk;
> +		u8 sysdiv, prediv, mult, pll_rdiv;
> +
> +		pll = rate * OV5640_SCLK_ROOT_DIVIDER_DEFAULT * _pclk_rdiv;
> +		pll = ov5640_calc_pll(sensor, pll, &sysdiv, &prediv, &pll_rdiv,
> +				      &mult);
> +
> +		pclk = pll / OV5640_SCLK_ROOT_DIVIDER_DEFAULT / _pclk_rdiv;
> +		if (abs(rate - pclk) < abs(rate - best)) {
> +			best = pclk;
> +			best_sysdiv = sysdiv;
> +			best_prediv = prediv;
> +			best_pll_rdiv = pll_rdiv;
> +			best_pclk_rdiv = _pclk_rdiv;
> +			best_mult = mult;
> +		}
> +
> +		if (pclk == rate)
> +			goto out;
> +	}
> +
> +out:
> +	*sysdiv = best_sysdiv;
> +	*prediv = best_prediv;
> +	*pll_rdiv = best_pll_rdiv;
> +	*mult = best_mult;
> +	*pclk_rdiv = best_pclk_rdiv;
> +	return best;
> +}
> +
> +static int ov5640_set_dvp_pclk(struct ov5640_dev *sensor, unsigned long rate)
> +{
> +	u8 sysdiv, prediv, mult, pll_rdiv, pclk_rdiv;
> +	int ret;
> +
> +	ov5640_calc_pclk(sensor, rate, &sysdiv, &prediv, &pll_rdiv, &mult,
> +			 &pclk_rdiv);
> +	ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1,
> +			     0xf0, sysdiv << 4);
> +	if (ret)
> +		return ret;
> +
> +	ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2,
> +			     0xff, mult);
> +	if (ret)
> +		return ret;
> +
> +	ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3,
> +			     0xff, prediv | ((pll_rdiv - 1) << 4));
> +	if (ret)
> +		return ret;
> +
> +	return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER,
> +			      0x30, ilog2(pclk_rdiv) << 4);
> +}
> +
> +static int ov5640_set_mipi_pclk(struct ov5640_dev *sensor, unsigned long rate)
> +{
> +	u8 sysdiv, prediv, mult, pll_rdiv, pclk_rdiv;
> +	int ret;
> +
> +	ov5640_calc_pclk(sensor, rate, &sysdiv, &prediv, &pll_rdiv, &mult,
> +			 &pclk_rdiv);
> +	ret = ov5640_write_reg(sensor, OV5640_REG_SC_PLL_CTRL1,
> +			       (sysdiv << 4) | pclk_rdiv);
> +	if (ret)
> +		return ret;
> +
> +	ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2,
> +			     0xff, mult);
> +	if (ret)
> +		return ret;
> +
> +	return ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3,
> +			      0xff, prediv | ((pll_rdiv - 1) << 4));
> +}
> +
>  /* download ov5640 settings to sensor through i2c */
>  static int ov5640_load_regs(struct ov5640_dev *sensor,
>  			    const struct ov5640_mode_info *mode)
> @@ -1610,6 +1868,14 @@ static int ov5640_set_mode(struct ov5640_dev *sensor,
>  	if (ret)
>  		return ret;
>  
> +	if (sensor->ep.bus_type == V4L2_MBUS_CSI2)
> +		ret = ov5640_set_mipi_pclk(sensor, mode->clock);
> +	else
> +		ret = ov5640_set_dvp_pclk(sensor, mode->clock);
> +
> +	if (ret < 0)
> +		return 0;
> +
>  	if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) ||
>  	    (dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) {
>  		/*

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@xxxxxx



[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux