Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (c) 2018 BayLibre, SAS.
0004  * Author: Neil Armstrong <narmstrong@baylibre.com>
0005  */
0006 
0007 #include <linux/clk-provider.h>
0008 #include <linux/module.h>
0009 
0010 #include "clk-regmap.h"
0011 #include "vid-pll-div.h"
0012 
0013 static inline struct meson_vid_pll_div_data *
0014 meson_vid_pll_div_data(struct clk_regmap *clk)
0015 {
0016     return (struct meson_vid_pll_div_data *)clk->data;
0017 }
0018 
0019 /*
0020  * This vid_pll divided is a fully programmable fractionnal divider to
0021  * achieve complex video clock rates.
0022  *
0023  * Here are provided the commonly used fraction values provided by Amlogic.
0024  */
0025 
0026 struct vid_pll_div {
0027     unsigned int shift_val;
0028     unsigned int shift_sel;
0029     unsigned int divider;
0030     unsigned int multiplier;
0031 };
0032 
0033 #define VID_PLL_DIV(_val, _sel, _ft, _fb)               \
0034     {                               \
0035         .shift_val = (_val),                    \
0036         .shift_sel = (_sel),                    \
0037         .divider = (_ft),                   \
0038         .multiplier = (_fb),                    \
0039     }
0040 
0041 static const struct vid_pll_div vid_pll_div_table[] = {
0042     VID_PLL_DIV(0x0aaa, 0, 2, 1),   /* 2/1  => /2 */
0043     VID_PLL_DIV(0x5294, 2, 5, 2),   /* 5/2  => /2.5 */
0044     VID_PLL_DIV(0x0db6, 0, 3, 1),   /* 3/1  => /3 */
0045     VID_PLL_DIV(0x36cc, 1, 7, 2),   /* 7/2  => /3.5 */
0046     VID_PLL_DIV(0x6666, 2, 15, 4),  /* 15/4 => /3.75 */
0047     VID_PLL_DIV(0x0ccc, 0, 4, 1),   /* 4/1  => /4 */
0048     VID_PLL_DIV(0x739c, 2, 5, 1),   /* 5/1  => /5 */
0049     VID_PLL_DIV(0x0e38, 0, 6, 1),   /* 6/1  => /6 */
0050     VID_PLL_DIV(0x0000, 3, 25, 4),  /* 25/4 => /6.25 */
0051     VID_PLL_DIV(0x3c78, 1, 7, 1),   /* 7/1  => /7 */
0052     VID_PLL_DIV(0x78f0, 2, 15, 2),  /* 15/2 => /7.5 */
0053     VID_PLL_DIV(0x0fc0, 0, 12, 1),  /* 12/1 => /12 */
0054     VID_PLL_DIV(0x3f80, 1, 14, 1),  /* 14/1 => /14 */
0055     VID_PLL_DIV(0x7f80, 2, 15, 1),  /* 15/1 => /15 */
0056 };
0057 
0058 #define to_meson_vid_pll_div(_hw) \
0059     container_of(_hw, struct meson_vid_pll_div, hw)
0060 
0061 static const struct vid_pll_div *_get_table_val(unsigned int shift_val,
0062                         unsigned int shift_sel)
0063 {
0064     int i;
0065 
0066     for (i = 0 ; i < ARRAY_SIZE(vid_pll_div_table) ; ++i) {
0067         if (vid_pll_div_table[i].shift_val == shift_val &&
0068             vid_pll_div_table[i].shift_sel == shift_sel)
0069             return &vid_pll_div_table[i];
0070     }
0071 
0072     return NULL;
0073 }
0074 
0075 static unsigned long meson_vid_pll_div_recalc_rate(struct clk_hw *hw,
0076                            unsigned long parent_rate)
0077 {
0078     struct clk_regmap *clk = to_clk_regmap(hw);
0079     struct meson_vid_pll_div_data *pll_div = meson_vid_pll_div_data(clk);
0080     const struct vid_pll_div *div;
0081 
0082     div = _get_table_val(meson_parm_read(clk->map, &pll_div->val),
0083                  meson_parm_read(clk->map, &pll_div->sel));
0084     if (!div || !div->divider) {
0085         pr_debug("%s: Invalid config value for vid_pll_div\n", __func__);
0086         return 0;
0087     }
0088 
0089     return DIV_ROUND_UP_ULL(parent_rate * div->multiplier, div->divider);
0090 }
0091 
0092 const struct clk_ops meson_vid_pll_div_ro_ops = {
0093     .recalc_rate    = meson_vid_pll_div_recalc_rate,
0094 };
0095 EXPORT_SYMBOL_GPL(meson_vid_pll_div_ro_ops);
0096 
0097 MODULE_DESCRIPTION("Amlogic video pll divider driver");
0098 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
0099 MODULE_LICENSE("GPL v2");