// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2020 NXP
* Copyright 2022 Pengutronix , Lucas Stach < kernel @ pengutronix . de >
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#define PHY_REG(reg) (reg * 4 )
#define REG01_PMS_P_MASK GENMASK(3 , 0 )
#define REG03_PMS_S_MASK GENMASK(7 , 4 )
#define REG12_CK_DIV_MASK GENMASK(5 , 4 )
#define REG13_TG_CODE_LOW_MASK GENMASK(7 , 0 )
#define REG14_TOL_MASK GENMASK(7 , 4 )
#define REG14_RP_CODE_MASK GENMASK(3 , 1 )
#define REG14_TG_CODE_HIGH_MASK GENMASK(0 , 0 )
#define REG21_SEL_TX_CK_INV BIT(7 )
#define REG21_PMS_S_MASK GENMASK(3 , 0 )
/*
* REG33 does not match the ref manual . According to Sandor Yu from NXP ,
* " There is a doc issue on the i . MX8MP latest RM "
* REG33 is being used per guidance from Sandor
*/
#define REG33_MODE_SET_DONE BIT(7 )
#define REG33_FIX_DA BIT(1 )
#define REG34_PHY_READY BIT(7 )
#define REG34_PLL_LOCK BIT(6 )
#define REG34_PHY_CLK_READY BIT(5 )
#ifndef MHZ
#define MHZ (1000 UL * 1000 UL)
#endif
#define PHY_PLL_DIV_REGS_NUM 7
struct phy_config {
u32 pixclk;
u8 pll_div_regs[PHY_PLL_DIV_REGS_NUM];
};
/*
* The calculated_phy_pll_cfg only handles integer divider for PMS ,
* meaning the last four entries will be fixed , but the first three will
* be calculated by the PMS calculator .
*/
static struct phy_config calculated_phy_pll_cfg = {
.pixclk = 0 ,
.pll_div_regs = { 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x80, 0 x00 },
};
/* The lookup table contains values for which the fractional divder is used */
static const struct phy_config phy_pll_cfg[] = {
{
.pixclk = 22250000 ,
.pll_div_regs = { 0 xd1, 0 x4b, 0 xf1, 0 x89, 0 x88, 0 x80, 0 x40 },
}, {
.pixclk = 23750000 ,
.pll_div_regs = { 0 xd1, 0 x50, 0 xf1, 0 x86, 0 x85, 0 x80, 0 x40 },
}, {
.pixclk = 24024000 ,
.pll_div_regs = { 0 xd1, 0 x50, 0 xf1, 0 x99, 0 x02, 0 x80, 0 x40 },
}, {
.pixclk = 25175000 ,
.pll_div_regs = { 0 xd1, 0 x54, 0 xfc, 0 xcc, 0 x91, 0 x80, 0 x40 },
}, {
.pixclk = 26750000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 xf2, 0 x89, 0 x88, 0 x80, 0 x40 },
}, {
.pixclk = 27027000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 xf2, 0 xfd, 0 x0c, 0 x80, 0 x40 },
}, {
.pixclk = 29500000 ,
.pll_div_regs = { 0 xd1, 0 x62, 0 xf4, 0 x95, 0 x08, 0 x80, 0 x40 },
}, {
.pixclk = 30750000 ,
.pll_div_regs = { 0 xd1, 0 x66, 0 xf4, 0 x82, 0 x01, 0 x88, 0 x45 },
}, {
.pixclk = 30888000 ,
.pll_div_regs = { 0 xd1, 0 x66, 0 xf4, 0 x99, 0 x18, 0 x88, 0 x45 },
}, {
.pixclk = 33750000 ,
.pll_div_regs = { 0 xd1, 0 x70, 0 xf4, 0 x82, 0 x01, 0 x80, 0 x40 },
}, {
.pixclk = 35000000 ,
.pll_div_regs = { 0 xd1, 0 x58, 0 xb8, 0 x8b, 0 x88, 0 x80, 0 x40 },
}, {
.pixclk = 36036000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 xb2, 0 xfd, 0 x0c, 0 x80, 0 x40 },
}, {
.pixclk = 43243200 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 x92, 0 xfd, 0 x0c, 0 x80, 0 x40 },
}, {
.pixclk = 44500000 ,
.pll_div_regs = { 0 xd1, 0 x5c, 0 x92, 0 x98, 0 x11, 0 x84, 0 x41 },
}, {
.pixclk = 47000000 ,
.pll_div_regs = { 0 xd1, 0 x62, 0 x94, 0 x95, 0 x82, 0 x80, 0 x40 },
}, {
.pixclk = 47500000 ,
.pll_div_regs = { 0 xd1, 0 x63, 0 x96, 0 xa1, 0 x82, 0 x80, 0 x40 },
}, {
.pixclk = 50349650 ,
.pll_div_regs = { 0 xd1, 0 x54, 0 x7c, 0 xc3, 0 x8f, 0 x80, 0 x40 },
}, {
.pixclk = 53250000 ,
.pll_div_regs = { 0 xd1, 0 x58, 0 x72, 0 x84, 0 x03, 0 x82, 0 x41 },
}, {
.pixclk = 53500000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 x72, 0 x89, 0 x88, 0 x80, 0 x40 },
}, {
.pixclk = 54054000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 x72, 0 xfd, 0 x0c, 0 x80, 0 x40 },
}, {
.pixclk = 59000000 ,
.pll_div_regs = { 0 xd1, 0 x62, 0 x74, 0 x95, 0 x08, 0 x80, 0 x40 },
}, {
.pixclk = 59340659 ,
.pll_div_regs = { 0 xd1, 0 x62, 0 x74, 0 xdb, 0 x52, 0 x88, 0 x47 },
}, {
.pixclk = 61500000 ,
.pll_div_regs = { 0 xd1, 0 x66, 0 x74, 0 x82, 0 x01, 0 x88, 0 x45 },
}, {
.pixclk = 63500000 ,
.pll_div_regs = { 0 xd1, 0 x69, 0 x74, 0 x89, 0 x08, 0 x80, 0 x40 },
}, {
.pixclk = 67500000 ,
.pll_div_regs = { 0 xd1, 0 x54, 0 x52, 0 x87, 0 x03, 0 x80, 0 x40 },
}, {
.pixclk = 70000000 ,
.pll_div_regs = { 0 xd1, 0 x58, 0 x58, 0 x8b, 0 x88, 0 x80, 0 x40 },
}, {
.pixclk = 72072000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 x52, 0 xfd, 0 x0c, 0 x80, 0 x40 },
}, {
.pixclk = 74176000 ,
.pll_div_regs = { 0 xd1, 0 x5d, 0 x58, 0 xdb, 0 xA2, 0 x88, 0 x41 },
}, {
.pixclk = 74250000 ,
.pll_div_regs = { 0 xd1, 0 x5c, 0 x52, 0 x90, 0 x0d, 0 x84, 0 x41 },
}, {
.pixclk = 78500000 ,
.pll_div_regs = { 0 xd1, 0 x62, 0 x54, 0 x87, 0 x01, 0 x80, 0 x40 },
}, {
.pixclk = 82000000 ,
.pll_div_regs = { 0 xd1, 0 x66, 0 x54, 0 x82, 0 x01, 0 x88, 0 x45 },
}, {
.pixclk = 82500000 ,
.pll_div_regs = { 0 xd1, 0 x67, 0 x54, 0 x88, 0 x01, 0 x90, 0 x49 },
}, {
.pixclk = 89000000 ,
.pll_div_regs = { 0 xd1, 0 x70, 0 x54, 0 x84, 0 x83, 0 x80, 0 x40 },
}, {
.pixclk = 90000000 ,
.pll_div_regs = { 0 xd1, 0 x70, 0 x54, 0 x82, 0 x01, 0 x80, 0 x40 },
}, {
.pixclk = 94000000 ,
.pll_div_regs = { 0 xd1, 0 x4e, 0 x32, 0 xa7, 0 x10, 0 x80, 0 x40 },
}, {
.pixclk = 95000000 ,
.pll_div_regs = { 0 xd1, 0 x50, 0 x31, 0 x86, 0 x85, 0 x80, 0 x40 },
}, {
.pixclk = 98901099 ,
.pll_div_regs = { 0 xd1, 0 x52, 0 x3a, 0 xdb, 0 x4c, 0 x88, 0 x47 },
}, {
.pixclk = 99000000 ,
.pll_div_regs = { 0 xd1, 0 x52, 0 x32, 0 x82, 0 x01, 0 x88, 0 x47 },
}, {
.pixclk = 100699300 ,
.pll_div_regs = { 0 xd1, 0 x54, 0 x3c, 0 xc3, 0 x8f, 0 x80, 0 x40 },
}, {
.pixclk = 102500000 ,
.pll_div_regs = { 0 xd1, 0 x55, 0 x32, 0 x8c, 0 x05, 0 x90, 0 x4b },
}, {
.pixclk = 104750000 ,
.pll_div_regs = { 0 xd1, 0 x57, 0 x32, 0 x98, 0 x07, 0 x90, 0 x49 },
}, {
.pixclk = 106500000 ,
.pll_div_regs = { 0 xd1, 0 x58, 0 x32, 0 x84, 0 x03, 0 x82, 0 x41 },
}, {
.pixclk = 107000000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 x32, 0 x89, 0 x88, 0 x80, 0 x40 },
}, {
.pixclk = 108108000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 x32, 0 xfd, 0 x0c, 0 x80, 0 x40 },
}, {
.pixclk = 118000000 ,
.pll_div_regs = { 0 xd1, 0 x62, 0 x34, 0 x95, 0 x08, 0 x80, 0 x40 },
}, {
.pixclk = 123000000 ,
.pll_div_regs = { 0 xd1, 0 x66, 0 x34, 0 x82, 0 x01, 0 x88, 0 x45 },
}, {
.pixclk = 127000000 ,
.pll_div_regs = { 0 xd1, 0 x69, 0 x34, 0 x89, 0 x08, 0 x80, 0 x40 },
}, {
.pixclk = 135000000 ,
.pll_div_regs = { 0 xd1, 0 x70, 0 x34, 0 x82, 0 x01, 0 x80, 0 x40 },
}, {
.pixclk = 135580000 ,
.pll_div_regs = { 0 xd1, 0 x71, 0 x39, 0 xe9, 0 x82, 0 x9c, 0 x5b },
}, {
.pixclk = 137520000 ,
.pll_div_regs = { 0 xd1, 0 x72, 0 x38, 0 x99, 0 x10, 0 x85, 0 x41 },
}, {
.pixclk = 138750000 ,
.pll_div_regs = { 0 xd1, 0 x73, 0 x35, 0 x88, 0 x05, 0 x90, 0 x4d },
}, {
.pixclk = 140000000 ,
.pll_div_regs = { 0 xd1, 0 x75, 0 x36, 0 xa7, 0 x90, 0 x80, 0 x40 },
}, {
.pixclk = 148352000 ,
.pll_div_regs = { 0 xd1, 0 x7b, 0 x35, 0 xdb, 0 x39, 0 x90, 0 x45 },
}, {
.pixclk = 148500000 ,
.pll_div_regs = { 0 xd1, 0 x7b, 0 x35, 0 x84, 0 x03, 0 x90, 0 x45 },
}, {
.pixclk = 154000000 ,
.pll_div_regs = { 0 xd1, 0 x40, 0 x18, 0 x83, 0 x01, 0 x00, 0 x40 },
}, {
.pixclk = 157000000 ,
.pll_div_regs = { 0 xd1, 0 x41, 0 x11, 0 xa7, 0 x14, 0 x80, 0 x40 },
}, {
.pixclk = 160000000 ,
.pll_div_regs = { 0 xd1, 0 x42, 0 x12, 0 xa1, 0 x20, 0 x80, 0 x40 },
}, {
.pixclk = 162000000 ,
.pll_div_regs = { 0 xd1, 0 x43, 0 x18, 0 x8b, 0 x08, 0 x96, 0 x55 },
}, {
.pixclk = 164000000 ,
.pll_div_regs = { 0 xd1, 0 x45, 0 x11, 0 x83, 0 x82, 0 x90, 0 x4b },
}, {
.pixclk = 165000000 ,
.pll_div_regs = { 0 xd1, 0 x45, 0 x11, 0 x84, 0 x81, 0 x90, 0 x4b },
}, {
.pixclk = 185625000 ,
.pll_div_regs = { 0 xd1, 0 x4e, 0 x12, 0 x9a, 0 x95, 0 x80, 0 x40 },
}, {
.pixclk = 188000000 ,
.pll_div_regs = { 0 xd1, 0 x4e, 0 x12, 0 xa7, 0 x10, 0 x80, 0 x40 },
}, {
.pixclk = 198000000 ,
.pll_div_regs = { 0 xd1, 0 x52, 0 x12, 0 x82, 0 x01, 0 x88, 0 x47 },
}, {
.pixclk = 205000000 ,
.pll_div_regs = { 0 xd1, 0 x55, 0 x12, 0 x8c, 0 x05, 0 x90, 0 x4b },
}, {
.pixclk = 209500000 ,
.pll_div_regs = { 0 xd1, 0 x57, 0 x12, 0 x98, 0 x07, 0 x90, 0 x49 },
}, {
.pixclk = 213000000 ,
.pll_div_regs = { 0 xd1, 0 x58, 0 x12, 0 x84, 0 x03, 0 x82, 0 x41 },
}, {
.pixclk = 216216000 ,
.pll_div_regs = { 0 xd1, 0 x5a, 0 x12, 0 xfd, 0 x0c, 0 x80, 0 x40 },
}, {
.pixclk = 254000000 ,
.pll_div_regs = { 0 xd1, 0 x69, 0 x14, 0 x89, 0 x08, 0 x80, 0 x40 },
}, {
.pixclk = 277500000 ,
.pll_div_regs = { 0 xd1, 0 x73, 0 x15, 0 x88, 0 x05, 0 x90, 0 x4d },
}, {
.pixclk = 297000000 ,
.pll_div_regs = { 0 xd1, 0 x7b, 0 x15, 0 x84, 0 x03, 0 x90, 0 x45 },
},
};
struct reg_settings {
u8 reg;
u8 val;
};
static const struct reg_settings common_phy_cfg[] = {
{ PHY_REG(0 ), 0 x00 },
/* PHY_REG(1-7) pix clk specific */
{ PHY_REG(8 ), 0 x4f }, { PHY_REG(9 ), 0 x30 },
{ PHY_REG(10 ), 0 x33 }, { PHY_REG(11 ), 0 x65 },
/* REG12 pixclk specific */
/* REG13 pixclk specific */
/* REG14 pixclk specific */
{ PHY_REG(15 ), 0 x80 }, { PHY_REG(16 ), 0 x6c },
{ PHY_REG(17 ), 0 xf2 }, { PHY_REG(18 ), 0 x67 },
{ PHY_REG(19 ), 0 x00 }, { PHY_REG(20 ), 0 x10 },
/* REG21 pixclk specific */
{ PHY_REG(22 ), 0 x30 }, { PHY_REG(23 ), 0 x32 },
{ PHY_REG(24 ), 0 x60 }, { PHY_REG(25 ), 0 x8f },
{ PHY_REG(26 ), 0 x00 }, { PHY_REG(27 ), 0 x00 },
{ PHY_REG(28 ), 0 x08 }, { PHY_REG(29 ), 0 x00 },
{ PHY_REG(30 ), 0 x00 }, { PHY_REG(31 ), 0 x00 },
{ PHY_REG(32 ), 0 x00 }, { PHY_REG(33 ), 0 x80 },
{ PHY_REG(34 ), 0 x00 }, { PHY_REG(35 ), 0 x00 },
{ PHY_REG(36 ), 0 x00 }, { PHY_REG(37 ), 0 x00 },
{ PHY_REG(38 ), 0 x00 }, { PHY_REG(39 ), 0 x00 },
{ PHY_REG(40 ), 0 x00 }, { PHY_REG(41 ), 0 xe0 },
{ PHY_REG(42 ), 0 x83 }, { PHY_REG(43 ), 0 x0f },
{ PHY_REG(44 ), 0 x3E }, { PHY_REG(45 ), 0 xf8 },
{ PHY_REG(46 ), 0 x00 }, { PHY_REG(47 ), 0 x00 }
};
struct fsl_samsung_hdmi_phy {
struct device *dev;
void __iomem *regs;
struct clk *apbclk;
struct clk *refclk;
/* clk provider */
struct clk_hw hw;
const struct phy_config *cur_cfg;
};
static inline struct fsl_samsung_hdmi_phy *
to_fsl_samsung_hdmi_phy(struct clk_hw *hw)
{
return container_of(hw, struct fsl_samsung_hdmi_phy, hw);
}
static int
fsl_samsung_hdmi_phy_configure_pll_lock_det(struct fsl_samsung_hdmi_phy *phy,
const struct phy_config *cfg)
{
u32 pclk = cfg->pixclk;
u32 fld_tg_code;
u32 int_pllclk;
u8 div;
/* Find int_pllclk speed */
for (div = 0 ; div < 4 ; div++) {
int_pllclk = pclk / (1 << div);
if (int_pllclk < (50 * MHZ))
break ;
}
if (unlikely(div == 4 ))
return -EINVAL;
writeb(FIELD_PREP(REG12_CK_DIV_MASK, div), phy->regs + PHY_REG(12 ));
/*
* Calculation for the frequency lock detector target code ( fld_tg_code )
* is based on reference manual register description of PHY_REG13
* ( 13 . 10 . 3 . 1 . 14 . 2 ) :
* 1 st ) Calculate int_pllclk which is determinded by FLD_CK_DIV
* 2 nd ) Increase resolution to avoid rounding issues
* 3 th ) Do the div ( 256 / Freq . of int_pllclk ) * 24
* 4 th ) Reduce the resolution and always round up since the NXP
* settings rounding up always too . TODO : Check if that is
* correct .
*/
fld_tg_code = DIV_ROUND_UP(24 * MHZ * 256 , int_pllclk);
/* FLD_TOL and FLD_RP_CODE taken from downstream driver */
writeb(FIELD_PREP(REG13_TG_CODE_LOW_MASK, fld_tg_code),
phy->regs + PHY_REG(13 ));
writeb(FIELD_PREP(REG14_TOL_MASK, 2 ) |
FIELD_PREP(REG14_RP_CODE_MASK, 2 ) |
FIELD_PREP(REG14_TG_CODE_HIGH_MASK, fld_tg_code >> 8 ),
phy->regs + PHY_REG(14 ));
return 0 ;
}
static unsigned long fsl_samsung_hdmi_phy_find_pms(unsigned long fout, u8 *p, u16 *m, u8 *s)
{
unsigned long best_freq = 0 ;
u32 min_delta = 0 xffffffff;
u8 _p, best_p;
u16 _m, best_m;
u8 _s, best_s;
/*
* Figure 13 - 78 of the reference manual states the PLL should be TMDS x 5
* while the TMDS_CLKO should be the PLL / 5 . So to calculate the PLL ,
* take the pix clock x 5 , then return the value of the PLL / 5 .
*/
fout *= 5 ;
/* The ref manual states the values of 'P' range from 1 to 11 */
for (_p = 1 ; _p <= 11 ; ++_p) {
for (_s = 1 ; _s <= 16 ; ++_s) {
u64 tmp;
u32 delta;
/* s must be one or even */
if (_s > 1 && (_s & 0 x01) == 1 )
_s++;
/* _s cannot be 14 per the TRM */
if (_s == 14 )
continue ;
/*
* The Ref manual doesn ' t explicitly state the range of M ,
* but it does show it as an 8 - bit value , so reject
* any value above 255 .
*/
tmp = (u64)fout * (_p * _s);
do_div(tmp, 24 * MHZ);
if (tmp > 255 )
continue ;
_m = tmp;
/*
* Rev 2 of the Ref Manual states the
* VCO can range between 750 MHz and
* 3 GHz . The VCO is assumed to be
* Fvco = ( M * f_ref ) / P ,
* where f_ref is 24 MHz .
*/
tmp = div64_ul((u64)_m * 24 * MHZ, _p);
if (tmp < 750 * MHZ ||
tmp > 3000 * MHZ)
continue ;
/* Final frequency after post-divider */
do_div(tmp, _s);
delta = abs(fout - tmp);
if (delta < min_delta) {
best_p = _p;
best_s = _s;
best_m = _m;
min_delta = delta;
best_freq = tmp;
}
/* If we have an exact match, stop looking for a better value */
if (!delta)
goto done;
}
}
done:
if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
}
return best_freq / 5 ;
}
static int fsl_samsung_hdmi_phy_configure(struct fsl_samsung_hdmi_phy *phy,
const struct phy_config *cfg)
{
int i, ret;
u8 val;
phy->cur_cfg = cfg;
/* HDMI PHY init */
writeb(REG33_FIX_DA, phy->regs + PHY_REG(33 ));
/* common PHY registers */
for (i = 0 ; i < ARRAY_SIZE(common_phy_cfg); i++)
writeb(common_phy_cfg[i].val, phy->regs + common_phy_cfg[i].reg);
/* set individual PLL registers PHY_REG1 ... PHY_REG7 */
for (i = 0 ; i < PHY_PLL_DIV_REGS_NUM; i++)
writeb(cfg->pll_div_regs[i], phy->regs + PHY_REG(1 ) + i * 4 );
/* High nibble of PHY_REG3 and low nibble of PHY_REG21 both contain 'S' */
writeb(REG21_SEL_TX_CK_INV | FIELD_PREP(REG21_PMS_S_MASK,
cfg->pll_div_regs[2 ] >> 4 ), phy->regs + PHY_REG(21 ));
ret = fsl_samsung_hdmi_phy_configure_pll_lock_det(phy, cfg);
if (ret) {
dev_err(phy->dev, "pixclock too large\n" );
return ret;
}
writeb(REG33_FIX_DA | REG33_MODE_SET_DONE, phy->regs + PHY_REG(33 ));
ret = readb_poll_timeout(phy->regs + PHY_REG(34 ), val,
val & REG34_PLL_LOCK, 50 , 20000 );
if (ret)
dev_err(phy->dev, "PLL failed to lock\n" );
return ret;
}
static unsigned long phy_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw);
if (!phy->cur_cfg)
return 74250000 ;
return phy->cur_cfg->pixclk;
}
/* Helper function to lookup the available fractional-divider rate */
static const struct phy_config *fsl_samsung_hdmi_phy_lookup_rate(unsigned long rate)
{
int i;
/* Search the lookup table */
for (i = ARRAY_SIZE(phy_pll_cfg) - 1 ; i >= 0 ; i--)
if (phy_pll_cfg[i].pixclk <= rate)
break ;
/* If there is an exact match, or the array has been searched, return the value*/
if (phy_pll_cfg[i].pixclk == rate || i + 1 > ARRAY_SIZE(phy_pll_cfg) - 1 )
return &phy_pll_cfg[i];
/* See if the next entry is closer to nominal than this one */
return (abs((long ) rate - (long ) phy_pll_cfg[i].pixclk) <
abs((long ) rate - (long ) phy_pll_cfg[i+1 ].pixclk) ?
&phy_pll_cfg[i] : &phy_pll_cfg[i+1 ]);
}
static void fsl_samsung_hdmi_calculate_phy(struct phy_config *cal_phy, unsigned long rate,
u8 p, u16 m, u8 s)
{
cal_phy->pixclk = rate;
cal_phy->pll_div_regs[0 ] = FIELD_PREP(REG01_PMS_P_MASK, p);
cal_phy->pll_div_regs[1 ] = m;
cal_phy->pll_div_regs[2 ] = FIELD_PREP(REG03_PMS_S_MASK, s-1 );
/* pll_div_regs 3-6 are fixed and pre-defined already */
}
static
const struct phy_config *fsl_samsung_hdmi_phy_find_settings(struct fsl_samsung_hdmi_phy *phy,
unsigned long rate)
{
const struct phy_config *fract_div_phy;
u32 int_div_clk;
u16 m;
u8 p, s;
/* If the clock is out of range return error instead of searching */
if (rate > 297000000 || rate < 22250000 )
return NULL;
/* Search the fractional divider lookup table */
fract_div_phy = fsl_samsung_hdmi_phy_lookup_rate(rate);
if (fract_div_phy->pixclk == rate) {
dev_dbg(phy->dev, "fractional divider match = %u\n" , fract_div_phy->pixclk);
return fract_div_phy;
}
/* Calculate the integer divider */
int_div_clk = fsl_samsung_hdmi_phy_find_pms(rate, &p, &m, &s);
fsl_samsung_hdmi_calculate_phy(&calculated_phy_pll_cfg, int_div_clk, p, m, s);
if (int_div_clk == rate) {
dev_dbg(phy->dev, "integer divider match = %u\n" , calculated_phy_pll_cfg.pixclk);
return &calculated_phy_pll_cfg;
}
/* Calculate the absolute value of the differences and return whichever is closest */
if (abs((long )rate - (long )int_div_clk) <
abs((long )rate - (long )fract_div_phy->pixclk)) {
dev_dbg(phy->dev, "integer divider = %u\n" , calculated_phy_pll_cfg.pixclk);
return &calculated_phy_pll_cfg;
}
dev_dbg(phy->dev, "fractional divider = %u\n" , phy->cur_cfg->pixclk);
return fract_div_phy;
}
static long fsl_samsung_hdmi_phy_clk_round_rate(struct clk_hw *hw,
unsigned long rate, unsigned long *parent_rate)
{
struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw);
const struct phy_config *target_settings = fsl_samsung_hdmi_phy_find_settings(phy, rate);
if (target_settings == NULL)
return -EINVAL;
dev_dbg(phy->dev, "round_rate, closest rate = %u\n" , target_settings->pixclk);
return target_settings->pixclk;
}
static int fsl_samsung_hdmi_phy_clk_set_rate(struct clk_hw *hw,
unsigned long rate, unsigned long parent_rate)
{
struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw);
const struct phy_config *target_settings = fsl_samsung_hdmi_phy_find_settings(phy, rate);
if (target_settings == NULL)
return -EINVAL;
dev_dbg(phy->dev, "set_rate, closest rate = %u\n" , target_settings->pixclk);
return fsl_samsung_hdmi_phy_configure(phy, target_settings);
}
static const struct clk_ops phy_clk_ops = {
.recalc_rate = phy_clk_recalc_rate,
.round_rate = fsl_samsung_hdmi_phy_clk_round_rate,
.set_rate = fsl_samsung_hdmi_phy_clk_set_rate,
};
static int phy_clk_register(struct fsl_samsung_hdmi_phy *phy)
{
struct device *dev = phy->dev;
struct device_node *np = dev->of_node;
struct clk_init_data init;
const char *parent_name;
struct clk *phyclk;
int ret;
parent_name = __clk_get_name(phy->refclk);
init.parent_names = &parent_name;
init.num_parents = 1 ;
init.flags = 0 ;
init.name = "hdmi_pclk" ;
init.ops = &phy_clk_ops;
phy->hw.init = &init;
phyclk = devm_clk_register(dev, &phy->hw);
if (IS_ERR(phyclk))
return dev_err_probe(dev, PTR_ERR(phyclk),
"failed to register clock\n" );
ret = of_clk_add_hw_provider(np, of_clk_hw_simple_get, phyclk);
if (ret)
return dev_err_probe(dev, ret,
"failed to register clock provider\n" );
return 0 ;
}
static int fsl_samsung_hdmi_phy_probe(struct platform_device *pdev)
{
struct fsl_samsung_hdmi_phy *phy;
int ret;
phy = devm_kzalloc(&pdev->dev, sizeof (*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
platform_set_drvdata(pdev, phy);
phy->dev = &pdev->dev;
phy->regs = devm_platform_ioremap_resource(pdev, 0 );
if (IS_ERR(phy->regs))
return PTR_ERR(phy->regs);
phy->apbclk = devm_clk_get_enabled(phy->dev, "apb" );
if (IS_ERR(phy->apbclk))
return dev_err_probe(phy->dev, PTR_ERR(phy->apbclk),
"failed to get apb clk\n" );
phy->refclk = devm_clk_get(phy->dev, "ref" );
if (IS_ERR(phy->refclk))
return dev_err_probe(phy->dev, PTR_ERR(phy->refclk),
"failed to get ref clk\n" );
pm_runtime_get_noresume(phy->dev);
pm_runtime_set_active(phy->dev);
pm_runtime_enable(phy->dev);
ret = phy_clk_register(phy);
if (ret) {
dev_err(&pdev->dev, "register clk failed\n" );
goto register_clk_failed;
}
pm_runtime_put(phy->dev);
return 0 ;
register_clk_failed:
return ret;
}
static void fsl_samsung_hdmi_phy_remove(struct platform_device *pdev)
{
of_clk_del_provider(pdev->dev.of_node);
}
static int __maybe_unused fsl_samsung_hdmi_phy_suspend(struct device *dev)
{
struct fsl_samsung_hdmi_phy *phy = dev_get_drvdata(dev);
clk_disable_unprepare(phy->apbclk);
return 0 ;
}
static int __maybe_unused fsl_samsung_hdmi_phy_resume(struct device *dev)
{
struct fsl_samsung_hdmi_phy *phy = dev_get_drvdata(dev);
int ret = 0 ;
ret = clk_prepare_enable(phy->apbclk);
if (ret) {
dev_err(phy->dev, "failed to enable apbclk\n" );
return ret;
}
if (phy->cur_cfg)
ret = fsl_samsung_hdmi_phy_configure(phy, phy->cur_cfg);
return ret;
}
static DEFINE_RUNTIME_DEV_PM_OPS(fsl_samsung_hdmi_phy_pm_ops,
fsl_samsung_hdmi_phy_suspend,
fsl_samsung_hdmi_phy_resume, NULL);
static const struct of_device_id fsl_samsung_hdmi_phy_of_match[] = {
{
.compatible = "fsl,imx8mp-hdmi-phy" ,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, fsl_samsung_hdmi_phy_of_match);
static struct platform_driver fsl_samsung_hdmi_phy_driver = {
.probe = fsl_samsung_hdmi_phy_probe,
.remove = fsl_samsung_hdmi_phy_remove,
.driver = {
.name = "fsl-samsung-hdmi-phy" ,
.of_match_table = fsl_samsung_hdmi_phy_of_match,
.pm = pm_ptr(&fsl_samsung_hdmi_phy_pm_ops),
},
};
module_platform_driver(fsl_samsung_hdmi_phy_driver);
MODULE_AUTHOR("Sandor Yu <Sandor.yu@nxp.com>" );
MODULE_DESCRIPTION("SAMSUNG HDMI 2.0 Transmitter PHY Driver" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=96 H=94 G=94
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland