|  | /* | 
|  | * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. All Rights Reserved. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * The code contained herein is licensed under the GNU General Public | 
|  | * License. You may obtain a copy of the GNU General Public License | 
|  | * Version 2 or later at the following locations: | 
|  | * | 
|  | * http://www.opensource.org/licenses/gpl-license.html | 
|  | * http://www.gnu.org/copyleft/gpl.html | 
|  | */ | 
|  |  | 
|  | /*! | 
|  | * @file ipu_pixel_clk.c | 
|  | * | 
|  | * @brief IPU pixel clock implementation | 
|  | * | 
|  | * @ingroup IPU | 
|  | */ | 
|  |  | 
|  | #include <linux/clk-provider.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/ipu-v3.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/string.h> | 
|  |  | 
|  | #include "ipu_prv.h" | 
|  | #include "ipu_regs.h" | 
|  |  | 
|  | /* | 
|  | * muxd clock implementation | 
|  | */ | 
|  | struct clk_di_mux { | 
|  | struct clk_hw hw; | 
|  | u8		ipu_id; | 
|  | u8		di_id; | 
|  | u8		flags; | 
|  | u8		index; | 
|  | }; | 
|  | #define to_clk_di_mux(_hw) container_of(_hw, struct clk_di_mux, hw) | 
|  |  | 
|  | static int _ipu_pixel_clk_set_parent(struct clk_hw *hw, u8 index) | 
|  | { | 
|  | struct clk_di_mux *mux = to_clk_di_mux(hw); | 
|  | struct ipu_soc *ipu = ipu_get_soc(mux->ipu_id); | 
|  | u32 di_gen; | 
|  |  | 
|  | di_gen = ipu_di_read(ipu, mux->di_id, DI_GENERAL); | 
|  | if (index == 0) | 
|  | /* ipu1_clk or ipu2_clk internal clk */ | 
|  | di_gen &= ~DI_GEN_DI_CLK_EXT; | 
|  | else | 
|  | di_gen |= DI_GEN_DI_CLK_EXT; | 
|  |  | 
|  | ipu_di_write(ipu, mux->di_id, di_gen, DI_GENERAL); | 
|  | mux->index = index; | 
|  | pr_debug("ipu_pixel_clk: di_clk_ext:0x%x, di_gen reg:0x%x.\n", | 
|  | !(di_gen & DI_GEN_DI_CLK_EXT), di_gen); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static u8 _ipu_pixel_clk_get_parent(struct clk_hw *hw) | 
|  | { | 
|  | struct clk_di_mux *mux = to_clk_di_mux(hw); | 
|  |  | 
|  | return mux->index; | 
|  | } | 
|  |  | 
|  | const struct clk_ops clk_mux_di_ops = { | 
|  | .get_parent = _ipu_pixel_clk_get_parent, | 
|  | .set_parent = _ipu_pixel_clk_set_parent, | 
|  | }; | 
|  |  | 
|  | struct clk *clk_register_mux_pix_clk(struct device *dev, const char *name, | 
|  | const char **parent_names, u8 num_parents, unsigned long flags, | 
|  | u8 ipu_id, u8 di_id, u8 clk_mux_flags) | 
|  | { | 
|  | struct clk_di_mux *mux; | 
|  | struct clk *clk; | 
|  | struct clk_init_data init; | 
|  |  | 
|  | mux = kzalloc(sizeof(struct clk_di_mux), GFP_KERNEL); | 
|  | if (!mux) | 
|  | return ERR_PTR(-ENOMEM); | 
|  |  | 
|  | init.name = name; | 
|  | init.ops = &clk_mux_di_ops; | 
|  | init.flags = flags; | 
|  | init.parent_names = parent_names; | 
|  | init.num_parents = num_parents; | 
|  |  | 
|  | mux->ipu_id = ipu_id; | 
|  | mux->di_id = di_id; | 
|  | mux->flags = clk_mux_flags | CLK_SET_RATE_PARENT; | 
|  | mux->hw.init = &init; | 
|  |  | 
|  | clk = clk_register(dev, &mux->hw); | 
|  | if (IS_ERR(clk)) | 
|  | kfree(mux); | 
|  |  | 
|  | return clk; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Gated clock implementation | 
|  | */ | 
|  | struct clk_di_div { | 
|  | struct clk_hw hw; | 
|  | u8		ipu_id; | 
|  | u8		di_id; | 
|  | u8		flags; | 
|  | }; | 
|  | #define to_clk_di_div(_hw) container_of(_hw, struct clk_di_div, hw) | 
|  |  | 
|  | static unsigned long _ipu_pixel_clk_div_recalc_rate(struct clk_hw *hw, | 
|  | unsigned long parent_rate) | 
|  | { | 
|  | struct clk_di_div *di_div = to_clk_di_div(hw); | 
|  | struct ipu_soc *ipu = ipu_get_soc(di_div->ipu_id); | 
|  | u32 div; | 
|  | u64 final_rate = (unsigned long long)parent_rate * 16; | 
|  |  | 
|  | _ipu_get(ipu); | 
|  | div = ipu_di_read(ipu, di_div->di_id, DI_BS_CLKGEN0); | 
|  | _ipu_put(ipu); | 
|  | pr_debug("ipu_di%d read BS_CLKGEN0 div:%d, final_rate:%lld, prate:%ld\n", | 
|  | di_div->di_id, div, final_rate, parent_rate); | 
|  |  | 
|  | if (div == 0) | 
|  | return 0; | 
|  | do_div(final_rate, div); | 
|  |  | 
|  | return (unsigned long)final_rate; | 
|  | } | 
|  |  | 
|  | static long _ipu_pixel_clk_div_round_rate(struct clk_hw *hw, unsigned long rate, | 
|  | unsigned long *parent_clk_rate) | 
|  | { | 
|  | u64 div, final_rate; | 
|  | u32 remainder; | 
|  | u64 parent_rate = (unsigned long long)(*parent_clk_rate) * 16; | 
|  |  | 
|  | /* | 
|  | * Calculate divider | 
|  | * Fractional part is 4 bits, | 
|  | * so simply multiply by 2^4 to get fractional part. | 
|  | */ | 
|  | div = parent_rate; | 
|  | remainder = do_div(div, rate); | 
|  | /* Round the divider value */ | 
|  | if (remainder > (rate/2)) | 
|  | div++; | 
|  | if (div < 0x10)            /* Min DI disp clock divider is 1 */ | 
|  | div = 0x10; | 
|  | if (div & ~0xFEF) | 
|  | div &= 0xFF8; | 
|  | else { | 
|  | /* Round up divider if it gets us closer to desired pix clk */ | 
|  | if ((div & 0xC) == 0xC) { | 
|  | div += 0x10; | 
|  | div &= ~0xF; | 
|  | } | 
|  | } | 
|  | final_rate = parent_rate; | 
|  | do_div(final_rate, div); | 
|  |  | 
|  | return final_rate; | 
|  | } | 
|  |  | 
|  | static int _ipu_pixel_clk_div_set_rate(struct clk_hw *hw, unsigned long rate, | 
|  | unsigned long parent_clk_rate) | 
|  | { | 
|  | struct clk_di_div *di_div = to_clk_di_div(hw); | 
|  | struct ipu_soc *ipu = ipu_get_soc(di_div->ipu_id); | 
|  | u64 div, parent_rate; | 
|  | u32 remainder; | 
|  |  | 
|  | parent_rate = (unsigned long long)parent_clk_rate * 16; | 
|  | div = parent_rate; | 
|  | remainder = do_div(div, rate); | 
|  | /* Round the divider value */ | 
|  | if (remainder > (rate/2)) | 
|  | div++; | 
|  |  | 
|  | /* Round up divider if it gets us closer to desired pix clk */ | 
|  | if ((div & 0xC) == 0xC) { | 
|  | div += 0x10; | 
|  | div &= ~0xF; | 
|  | } | 
|  | if (div > 0x1000) | 
|  | pr_err("Overflow, di:%d, DI_BS_CLKGEN0 div:0x%x\n", | 
|  | di_div->di_id, (u32)div); | 
|  | _ipu_get(ipu); | 
|  | ipu_di_write(ipu, di_div->di_id, (u32)div, DI_BS_CLKGEN0); | 
|  |  | 
|  | /* Setup pixel clock timing */ | 
|  | /* FIXME: needs to be more flexible */ | 
|  | /* Down time is half of period */ | 
|  | ipu_di_write(ipu, di_div->di_id, ((u32)div / 16) << 16, DI_BS_CLKGEN1); | 
|  | _ipu_put(ipu); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct clk_ops clk_div_ops = { | 
|  | .recalc_rate = _ipu_pixel_clk_div_recalc_rate, | 
|  | .round_rate = _ipu_pixel_clk_div_round_rate, | 
|  | .set_rate = _ipu_pixel_clk_div_set_rate, | 
|  | }; | 
|  |  | 
|  | struct clk *clk_register_div_pix_clk(struct device *dev, const char *name, | 
|  | const char *parent_name, unsigned long flags, | 
|  | u8 ipu_id, u8 di_id, u8 clk_div_flags) | 
|  | { | 
|  | struct clk_di_div *di_div; | 
|  | struct clk *clk; | 
|  | struct clk_init_data init; | 
|  |  | 
|  | di_div = kzalloc(sizeof(struct clk_di_div), GFP_KERNEL); | 
|  | if (!di_div) | 
|  | return ERR_PTR(-ENOMEM); | 
|  |  | 
|  | /* struct clk_di_div assignments */ | 
|  | di_div->ipu_id = ipu_id; | 
|  | di_div->di_id = di_id; | 
|  | di_div->flags = clk_div_flags; | 
|  |  | 
|  | init.name = name; | 
|  | init.ops = &clk_div_ops; | 
|  | init.flags = flags | CLK_SET_RATE_PARENT; | 
|  | init.parent_names = parent_name ? &parent_name : NULL; | 
|  | init.num_parents = parent_name ? 1 : 0; | 
|  |  | 
|  | di_div->hw.init = &init; | 
|  |  | 
|  | clk = clk_register(dev, &di_div->hw); | 
|  | if (IS_ERR(clk)) | 
|  | kfree(di_div); | 
|  |  | 
|  | return clk; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Gated clock implementation | 
|  | */ | 
|  | struct clk_di_gate { | 
|  | struct clk_hw hw; | 
|  | u8		ipu_id; | 
|  | u8		di_id; | 
|  | u8		flags; | 
|  | }; | 
|  | #define to_clk_di_gate(_hw) container_of(_hw, struct clk_di_gate, hw) | 
|  |  | 
|  | static int _ipu_pixel_clk_enable(struct clk_hw *hw) | 
|  | { | 
|  | struct clk_di_gate *gate = to_clk_di_gate(hw); | 
|  | struct ipu_soc *ipu = ipu_get_soc(gate->ipu_id); | 
|  | u32 disp_gen; | 
|  |  | 
|  | disp_gen = ipu_cm_read(ipu, IPU_DISP_GEN); | 
|  | disp_gen |= gate->di_id ? DI1_COUNTER_RELEASE : DI0_COUNTER_RELEASE; | 
|  | ipu_cm_write(ipu, disp_gen, IPU_DISP_GEN); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void _ipu_pixel_clk_disable(struct clk_hw *hw) | 
|  | { | 
|  | struct clk_di_gate *gate = to_clk_di_gate(hw); | 
|  | struct ipu_soc *ipu = ipu_get_soc(gate->ipu_id); | 
|  | u32 disp_gen; | 
|  |  | 
|  | disp_gen = ipu_cm_read(ipu, IPU_DISP_GEN); | 
|  | disp_gen &= gate->di_id ? ~DI1_COUNTER_RELEASE : ~DI0_COUNTER_RELEASE; | 
|  | ipu_cm_write(ipu, disp_gen, IPU_DISP_GEN); | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct clk_ops clk_gate_di_ops = { | 
|  | .enable = _ipu_pixel_clk_enable, | 
|  | .disable = _ipu_pixel_clk_disable, | 
|  | }; | 
|  |  | 
|  | struct clk *clk_register_gate_pix_clk(struct device *dev, const char *name, | 
|  | const char *parent_name, unsigned long flags, | 
|  | u8 ipu_id, u8 di_id, u8 clk_gate_flags) | 
|  | { | 
|  | struct clk_di_gate *gate; | 
|  | struct clk *clk; | 
|  | struct clk_init_data init; | 
|  |  | 
|  | gate = kzalloc(sizeof(struct clk_di_gate), GFP_KERNEL); | 
|  | if (!gate) | 
|  | return ERR_PTR(-ENOMEM); | 
|  |  | 
|  | gate->ipu_id = ipu_id; | 
|  | gate->di_id = di_id; | 
|  | gate->flags = clk_gate_flags; | 
|  |  | 
|  | init.name = name; | 
|  | init.ops = &clk_gate_di_ops; | 
|  | init.flags = flags | CLK_SET_RATE_PARENT; | 
|  | init.parent_names = parent_name ? &parent_name : NULL; | 
|  | init.num_parents = parent_name ? 1 : 0; | 
|  |  | 
|  | gate->hw.init = &init; | 
|  |  | 
|  | clk = clk_register(dev, &gate->hw); | 
|  | if (IS_ERR(clk)) | 
|  | kfree(gate); | 
|  |  | 
|  | return clk; | 
|  | } |