// SPDX-License-Identifier: GPL-2.0-only
/*
* drivers/media/i2c/ccs-pll.c
*
* Generic MIPI CCS/SMIA/SMIA++ PLL calculator
*
* Copyright (C) 2020 Intel Corporation
* Copyright (C) 2011--2012 Nokia Corporation
* Contact: Sakari Ailus <sakari.ailus@linux.intel.com>
*/
#include <linux/device.h>
#include <linux/gcd.h>
#include <linux/lcm.h>
#include <linux/module.h>
#include "ccs-pll.h"
/* Return an even number or one. */
static inline u32 clk_div_even(u32 a)
{
return max_t(u32, 1, a & ~1);
}
/* Return an even number or one. */
static inline u32 clk_div_even_up(u32 a)
{
if (a == 1)
return 1;
return (a + 1) & ~1;
}
static inline u32 is_one_or_even(u32 a)
{
if (a == 1)
return 1;
if (a & 1)
return 0;
return 1;
}
static inline u32 one_or_more(u32 a)
{
return a ?: 1;
}
static int bounds_check(struct device *dev, u32 val,
u32 min, u32 max, const char *prefix,
char *str)
{
if (val >= min && val <= max)
return 0;
dev_dbg(dev, "%s_%s out of bounds: %d (%d--%d)\n", prefix,
str, val, min, max);
return -EINVAL;
}
#define PLL_OP 1
#define PLL_VT 2
static const char *pll_string(unsigned int which)
{
switch (which) {
case PLL_OP:
return "op";
case PLL_VT:
return "vt";
}
return NULL;
}
#define PLL_FL(f) CCS_PLL_FLAG_##f
static void print_pll(struct device *dev, const struct ccs_pll *pll)
{
const struct {
const struct ccs_pll_branch_fr *fr;
const struct ccs_pll_branch_bk *bk;
unsigned int which;
} branches[] = {
{ &pll->vt_fr, &pll->vt_bk, PLL_VT },
{ &pll->op_fr, &pll->op_bk, PLL_OP }
}, *br;
unsigned int i;
dev_dbg(dev, "ext_clk_freq_hz\t\t%u\n", pll->ext_clk_freq_hz);
for (i = 0, br = branches; i < ARRAY_SIZE(branches); i++, br++) {
const char *s = pll_string(br->which);
if (pll->flags & CCS_PLL_FLAG_DUAL_PLL ||
br->which == PLL_VT) {
dev_dbg(dev, "%s_pre_pll_clk_div\t\t%u\n", s,
br->fr->pre_pll_clk_div);
dev_dbg(dev, "%s_pll_multiplier\t\t%u\n", s,
br->fr->pll_multiplier);
dev_dbg(dev, "%s_pll_ip_clk_freq_hz\t%u\n", s,
br->fr->pll_ip_clk_freq_hz);
dev_dbg(dev, "%s_pll_op_clk_freq_hz\t%u\n", s,
br->fr->pll_op_clk_freq_hz);
}
if (!(pll->flags & CCS_PLL_FLAG_NO_OP_CLOCKS) ||
br->which == PLL_VT) {
dev_dbg(dev, "%s_sys_clk_div\t\t%u\n", s,
br->bk->sys_clk_div);
dev_dbg(dev, "%s_pix_clk_div\t\t%u\n", s,
br->bk->pix_clk_div);
dev_dbg(dev, "%s_sys_clk_freq_hz\t%u\n", s,
br->bk->sys_clk_freq_hz);
dev_dbg(dev, "%s_pix_clk_freq_hz\t%u\n", s,
br->bk->pix_clk_freq_hz);
}
}
dev_dbg(dev, "pixel rate in pixel array:\t%u\n",
pll->pixel_rate_pixel_array);
dev_dbg(dev, "pixel rate on CSI-2 bus:\t%u\n",
pll->pixel_rate_csi);
}
static void print_pll_flags(struct device *dev, struct ccs_pll *pll)
{
dev_dbg(dev, "PLL flags%s%s%s%s%s%s%s%s%s%s%s\n",
pll->flags & PLL_FL(OP_PIX_CLOCK_PER_LANE) ? " op-pix-clock-per-lane" : "",
pll->flags & PLL_FL(EVEN_PLL_MULTIPLIER) ? " even-pll-multiplier" : "",
pll->flags & PLL_FL(NO_OP_CLOCKS) ? " no-op-clocks" : "",
pll->flags & PLL_FL(LANE_SPEED_MODEL) ? " lane-speed" : "",
pll->flags & PLL_FL(EXT_IP_PLL_DIVIDER) ?
" ext-ip-pll-divider" : "",
pll->flags & PLL_FL(FLEXIBLE_OP_PIX_CLK_DIV) ?
" flexible-op-pix-div" : "",
pll