/* * Copyright (C) 2013 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 #include #include #include #include #include #include #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(clk); 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(clk); return clk; }