/***************************************************************************** * * File name : clock-stx7141.c * Description : Low Level API - HW specific implementation * * COPYRIGHT (C) 2009 STMicroelectronics - All Rights Reserved * May be copied or modified under the terms of the GNU General Public * License V2 __ONLY__. See linux/COPYING for more information. * *****************************************************************************/ /* ----- Modification history (most recent first)---- 19/may/10 francesco.virlinzi@st.com/fabrice.charpentier@st.com Added several divisor factor on 656_1, DISP_HD. PIX_SD, etc 11/mar/10 fabrice.charpentier@st.com Added support of clockgen E/cable interface. Moved USB2 to clockgen F. 04/mar/10 fabrice.charpentier@st.com CLKA_PLL0, CLKB_FS0, CLKB_FS1, CLKC_FS0 identifiers removed. 12/feb/10 fabrice.charpentier@st.com clkgenc_enable()/clkgenc_disable()/clkgenc_xable_fsyn() revisited. 02/dec/09 francesco.virlinzi@st.com ClockGen B/C managed as bank/channel 26/nov/09 fabrice.charpentier@st.com Renamed CLKA_IC_COMPO_200 to CLKA_IC_TS_200. Compo is clocked from CLKA_IC_DISP_200. Replaced obsolete REGISTER_CLK() macro by _CLK(). 02/oct/08 francesco.virlinzi@st.com Realigned Linux coding style 17/sep/09 fabrice.charpentier@st.com Realigned on 7111 udpates + bug fixes + ident 24/aug/09 fabrice.charpentier@st.com Revisited. Aligned on 7111 updates. 10/Jul/09 - Review by Francesco Virlinzi Applyed all the LLA rules. Linux compliant */ /* Includes ----------------------------------------------------------------- */ #include #include #include #include #include "clock-stx7141.h" #include "clock-regs-stx7141.h" #include "clock-oslayer.h" #include "clock-common.h" #include "clock-utils.h" static int clkgena_observe(clk_t *clk_p, unsigned long *div_p); static int clkgenb_observe(clk_t *clk_p, unsigned long *div_p); static int clkgena_set_parent(clk_t *clk_p, clk_t *src_p); static int clkgenb_set_parent(clk_t *clk_p, clk_t *src_p); static int clkgenc_set_parent(clk_t *clk_p, clk_t *src_p); static int clkgend_set_parent(clk_t *clk_p, clk_t *src_p); static int clkgenf_set_parent(clk_t *clk_p, clk_t *src_p); static int clkgena_set_rate(clk_t *clk_p, unsigned long freq); static int clkgenb_set_rate(clk_t *clk_p, unsigned long freq); static int clkgenc_set_rate(clk_t *clk_p, unsigned long freq); static int clkgena_set_div(clk_t *clk_p, unsigned long *div_p); static int clkgenb_set_div(clk_t *clk_p, unsigned long *div_p); static int clkgenb_set_fsclock(clk_t *clk_p, unsigned long freq); static int clkgena_recalc(clk_t *clk_p); static int clkgenb_recalc(clk_t *clk_p); static int clkgenb_fsyn_recalc(clk_t *clk_p); static int clkgenc_recalc(clk_t *clk_p); static int clkgend_recalc(clk_t *clk_p); static int clkgene_recalc(clk_t *clk_p); static int clkgenf_recalc(clk_t *clk_p); /* Added to get infos for USB */ static int clkgena_enable(clk_t *clk_p); static int clkgenb_enable(clk_t *clk_p); static int clkgenc_enable(clk_t *clk_p); static int clkgene_enable(clk_t *clk_p); static int clkgenf_enable(clk_t *clk_p); static int clkgena_disable(clk_t *clk_p); static int clkgenb_disable(clk_t *clk_p); static int clkgenc_disable(clk_t *clk_p); static int clkgene_disable(clk_t *clk_p); static int clkgenf_disable(clk_t *clk_p); static unsigned long clkgena_get_measure(clk_t *clk_p); static int clktop_init(clk_t *clk_p); static int clkgena_init(clk_t *clk_p); static int clkgenb_init(clk_t *clk_p); static int clkgenc_init(clk_t *clk_p); static int clkgend_init(clk_t *clk_p); static int clkgene_init(clk_t *clk_p); static int clkgenf_init(clk_t *clk_p); /* Per boards top input clocks. */ #define OSC_CLKOSC 30 /* USB/lp osc */ #define IFE_MCLK 27 /* 27Mhz input for cable/048 */ _CLK_OPS(Top, "Top clocks", clktop_init, NULL, NULL, NULL, NULL, NULL, NULL, NULL, /* No measure function */ NULL ); _CLK_OPS(clkgena, "clockgen A", clkgena_init, clkgena_set_parent, clkgena_set_rate, clkgena_recalc, clkgena_enable, clkgena_disable, clkgena_observe, clkgena_get_measure, NULL ); _CLK_OPS(clkgenb, "Clockgen B/Video", clkgenb_init, clkgenb_set_parent, clkgenb_set_rate, clkgenb_recalc, clkgenb_enable, clkgenb_disable, clkgenb_observe, NULL, /* No measure function */ NULL ); _CLK_OPS(clkgenc, "Clockgen C/Audio", clkgenc_init, clkgenc_set_parent, clkgenc_set_rate, clkgenc_recalc, clkgenc_enable, clkgenc_disable, NULL, NULL, /* No measure function */ NULL ); _CLK_OPS(clkgend, "Clockgen D/LMI", clkgend_init, clkgend_set_parent, NULL, clkgend_recalc, NULL, NULL, NULL, NULL, /* No measure function */ NULL /* No observation point */ ); _CLK_OPS(clkgene, "Clockgen E/Cable card", clkgene_init, NULL, NULL, clkgene_recalc, clkgene_enable, clkgene_disable, NULL, NULL, /* No measure function */ NULL /* No observation point */ ); _CLK_OPS(clkgenf, "USB", clkgenf_init, clkgenf_set_parent, NULL, clkgenf_recalc, clkgenf_enable, /* on CLKE_USB48 - USB 1.1 */ clkgenf_disable, /* on CLKE_USB48 - USB 1.1 */ NULL, NULL, /* No measure function */ NULL /* No observation point */ ); /* Physical clocks description */ clk_t clk_clocks[] = { /* Top level clocks */ _CLK(CLK_SATA_OSC, &Top, 0, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), _CLK(CLK_SYSALT, &Top, 0, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), /* Clockgen A */ _CLK(CLKA_REF, &clkgena, 0, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), _CLK_P(CLKA_PLL0HS, &clkgena, 900000000, CLK_RATE_PROPAGATES, &clk_clocks[CLKA_REF]), _CLK_P(CLKA_PLL0LS, &clkgena, 450000000, CLK_RATE_PROPAGATES, &clk_clocks[CLKA_PLL0HS]), _CLK_P(CLKA_PLL1, &clkgena, 800000000, CLK_RATE_PROPAGATES, &clk_clocks[CLKA_REF]), _CLK(CLKA_IC_STNOC, &clkgena, 400000000, CLK_ALWAYS_ENABLED), _CLK(CLKA_FDMA0, &clkgena, 400000000, 0), _CLK(CLKA_FDMA1, &clkgena, 400000000, 0), _CLK(CLKA_FDMA2, &clkgena, 400000000, 0), _CLK(CLKA_SH4_ICK, &clkgena, 450000000, CLK_ALWAYS_ENABLED), _CLK(CLKA_SH4_ICK_498, &clkgena, 450000000, CLK_ALWAYS_ENABLED), _CLK(CLKA_LX_DMU_CPU, &clkgena, 450000000, 0), _CLK(CLKA_LX_AUD_CPU, &clkgena, 450000000, 0), _CLK(CLKA_IC_BDISP_200, &clkgena, 200000000, 0), _CLK(CLKA_IC_DISP_200, &clkgena, 200000000, 0), _CLK(CLKA_IC_IF_100, &clkgena, 100000000, CLK_ALWAYS_ENABLED), _CLK(CLKA_DISP_PIPE_200, &clkgena, 200000000, 0), _CLK(CLKA_BLIT_PROC, &clkgena, 266666666, 0), _CLK(CLKA_ETH_PHY, &clkgena, 25000000, 0), _CLK(CLKA_PCI, &clkgena, 66666666, 0), _CLK(CLKA_EMI_MASTER, &clkgena, 100000000, 0), _CLK(CLKA_IC_TS_200, &clkgena, 200000000, 0), _CLK(CLKA_IC_IF_200, &clkgena, 200000000, CLK_ALWAYS_ENABLED), /* Clockgen B */ _CLK(CLKB_REF, &clkgenb, 0, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), _CLK_P(CLKB_FS0_CH1, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_FS0_CH2, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_FS0_CH3, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_FS0_CH4, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_FS1_CH1, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_FS1_CH2, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_FS1_CH3, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_FS1_CH4, &clkgenb, 0, CLK_RATE_PROPAGATES, &clk_clocks[CLKB_REF]), _CLK_P(CLKB_TMDS_HDMI, &clkgenb, 0, 0, &clk_clocks[CLKB_FS0_CH1]), _CLK(CLKB_PIX_HD, &clkgenb, 0, 0), _CLK_P(CLKB_DISP_HD, &clkgenb, 0, 0, &clk_clocks[CLKB_FS0_CH1]), _CLK_P(CLKB_656, &clkgenb, 0, 0, &clk_clocks[CLKB_FS0_CH1]), _CLK(CLKB_GDP3, &clkgenb, 0, 0), _CLK_P(CLKB_DISP_ID, &clkgenb, 0, 0, &clk_clocks[CLKB_FS1_CH1]), _CLK(CLKB_PIX_SD, &clkgenb, 0, 0), /* TheCLKB_PIX_FROM_DVP uses dummy parent required paarent to get it*/ _CLK_P(CLKB_PIX_FROM_DVP, &clkgenb, 0, 0, &clk_clocks[CLKB_FS1_CH1]), _CLK(CLKB_DVP, &clkgenb, 0, 0), _CLK_P(CLKB_DSS, &clkgenb, 0, 0, &clk_clocks[CLKB_FS0_CH2]), _CLK_P(CLKB_PP, &clkgenb, 0, 0, &clk_clocks[CLKB_FS1_CH3]), _CLK(CLKB_150, &clkgenb, 0, 0), _CLK_P(CLKB_LPC, &clkgenb, 0, 0, &clk_clocks[CLKB_FS1_CH4]), /* Clockgen C (CKGCIO) */ _CLK(CLKC_REF, &clkgenc, 0, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), _CLK_P(CLKC_FS0_CH1, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]), _CLK_P(CLKC_FS0_CH2, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]), _CLK_P(CLKC_FS0_CH3, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]), _CLK_P(CLKC_FS0_CH4, &clkgenc, 0, 0, &clk_clocks[CLKC_REF]), /* Clockgen D (LMI) */ _CLK(CLKD_REF, &clkgend, 30000000, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), _CLK_P(CLKD_LMI2X, &clkgend, 800000000, 0, &clk_clocks[CLKD_REF]), /* Clockgen E/Cable interface. Part of 0498 IP. */ _CLK(CLKE_REF, &clkgene, 27000000, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), _CLK_P(CLKE_QAM, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_QAM0, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_QAM1, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_QAM2, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_IFE_IF, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_DOCSIS, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_MAC, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_MSCC, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_MSCC_HOST, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), _CLK_P(CLKE_TXCLK, &clkgene, 0, 0, &clk_clocks[CLKE_REF]), /* Clockgen F/USB2.0 controller */ _CLK(CLKF_REF, &clkgenf, 30000000, CLK_RATE_PROPAGATES | CLK_ALWAYS_ENABLED), _CLK_P(CLKF_USB_PHY, &clkgenf, 60000000, 0, &clk_clocks[CLKF_REF]), _CLK_P(CLKF_USB48, &clkgenf, 48000000, 0, &clk_clocks[CLKF_REF]), }; SYSCONF(SYS_STA, 1, 0, 0); SYSCONF(SYS_CFG, 6, 0, 0); SYSCONF(SYS_CFG, 4, 4, 5); SYSCONF(SYS_CFG, 4, 5, 5); SYSCONF(SYS_CFG, 4, 10, 10); SYSCONF(SYS_CFG, 11, 1, 8); SYSCONF(SYS_CFG, 11, 9, 11); SYSCONF(SYS_CFG, 36, 0, 5); SYSCONF(SYS_CFG, 36, 6, 12); SYSCONF(SYS_CFG, 40, 0, 0); SYSCONF(SYS_CFG, 40, 2, 2); /* * The Linux plat_clk_init function */ int __init plat_clk_init(void) { int ret; SYSCONF_CLAIM(SYS_STA, 1, 0, 0); if (chip_major_version() < 2) SYSCONF_CLAIM(SYS_CFG, 4, 5, 5); else { SYSCONF_CLAIM(SYS_CFG, 4, 4, 5); SYSCONF_CLAIM(SYS_CFG, 4, 10, 10); } SYSCONF_CLAIM(SYS_CFG, 6, 0, 0); SYSCONF_CLAIM(SYS_CFG, 11, 1, 8); SYSCONF_CLAIM(SYS_CFG, 11, 9, 11); SYSCONF_CLAIM(SYS_CFG, 36, 0, 5); SYSCONF_CLAIM(SYS_CFG, 36, 6, 12); SYSCONF_CLAIM(SYS_CFG, 40, 0, 0); SYSCONF_CLAIM(SYS_CFG, 40, 2, 2); ret = clk_register_table(clk_clocks, CLKB_REF, 1); if (ret) return ret; ret = clk_register_table(&clk_clocks[CLKB_REF], ARRAY_SIZE(clk_clocks)-CLKB_REF - 3, 0); /* turn-on USB-clks */ ret = clk_register_table(&clk_clocks[CLKF_REF], ARRAY_SIZE(clk_clocks)-CLKF_REF, 1); return ret; } /****************************************************************************** Top level clocks group ******************************************************************************/ /* ======================================================================== Name: clkgena_init Description: Read HW status to initialize 'clk_t' structure. Returns: 'clk_err_t' error code. ======================================================================== */ static int clktop_init(clk_t *clk_p) { if (!clk_p) return CLK_ERR_BAD_PARAMETER; clk_p->parent = NULL; /* Top recalc function */ switch (clk_p->id) { case CLK_SATA_OSC: clk_p->rate = OSC_CLKOSC * 1000000; break; case CLK_SYSALT: clk_p->rate = OSC_CLKOSC * 1000000; break; default: clk_p->rate = 0; break; } return CLK_ERR_NONE; } /****************************************************************************** CLOCKGEN A (CPU+interco+comms) clocks group ******************************************************************************/ /* ======================================================================== Name: clkgena_get_index Description: Returns index of given clockgenA clock and source reg infos Returns: idx==-1 if error, else >=0 ======================================================================== */ static int clkgena_get_index(int clkid, unsigned long *srcreg, int *shift) { int idx; /* Warning: This function assumes clock IDs are perfectly following real implementation order. Each "hole" has therefore to be filled with "CLKx_NOT_USED" */ if (clkid < CLKA_IC_STNOC || clkid > CLKA_IC_IF_200) return -1; idx = clkid - CLKA_IC_STNOC; if (idx <= 15) { *srcreg = CKGA_CLKOPSRC_SWITCH_CFG; *shift = idx * 2; } else { *srcreg = CKGA_CLKOPSRC_SWITCH_CFG2; *shift = (idx-16) * 2; } return idx; } /* ======================================================================== Name: clkgena_set_parent Description: Set clock source for clockgenA when possible Returns: 0=NO error ======================================================================== */ static int clkgena_set_parent(clk_t *clk_p, clk_t *src_p) { unsigned long clk_src, val; int idx, shift; unsigned long srcreg; if (!clk_p || !src_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKA_REF || clk_p->id > CLKA_IC_IF_200) return CLK_ERR_BAD_PARAMETER; switch (src_p->id) { case CLKA_REF: clk_src = 0; break; case CLKA_PLL0LS: case CLKA_PLL0HS: clk_src = 1; break; case CLKA_PLL1: clk_src = 2; break; default: return CLK_ERR_BAD_PARAMETER; } idx = clkgena_get_index(clk_p->id, &srcreg, &shift); if (idx == -1) return CLK_ERR_BAD_PARAMETER; val = CLK_READ(CKGA_BASE_ADDRESS + srcreg) & ~(0x3 << shift); val = val | (clk_src << shift); CLK_WRITE(CKGA_BASE_ADDRESS + srcreg, val); clk_p->parent = &clk_clocks[src_p->id]; return clkgena_recalc(clk_p); } /* ======================================================================== Name: clkgena_identify_parent Description: Identify parent clock for clockgen A clocks. Returns: Pointer on parent 'clk_t' structure. ======================================================================== */ static int clkgena_identify_parent(clk_t *clk_p) { int idx; unsigned long src_sel; unsigned long srcreg; int shift; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKA_REF) { src_sel = SYSCONF_READ(SYS_STA, 1, 0, 0); switch (src_sel) { case 0: clk_p->parent = &clk_clocks[CLK_SATA_OSC]; break; case 1: clk_p->parent = &clk_clocks[CLK_SYSALT]; break; default: break; } return 0; } if (clk_p->id < CLKA_IC_STNOC) /* Statically initialized with _CLK_P() macro */ return 0; /* Which divider to setup ? */ idx = clkgena_get_index(clk_p->id, &srcreg, &shift); if (idx == -1) return CLK_ERR_BAD_PARAMETER; /* Identifying source */ src_sel = (CLK_READ(CKGA_BASE_ADDRESS + srcreg) >> shift) & 0x3; switch (src_sel) { case 0: clk_p->parent = &clk_clocks[CLKA_REF]; break; case 1: if (idx <= 3) clk_p->parent = &clk_clocks[CLKA_PLL0HS]; else clk_p->parent = &clk_clocks[CLKA_PLL0LS]; break; case 2: clk_p->parent = &clk_clocks[CLKA_PLL1]; break; case 3: clk_p->parent = NULL; clk_p->rate = 0; break; } return 0; } /* ======================================================================== Name: clkgena_xable_pll Description: Enable/disable PLL Returns: 'clk_err_t' error code ======================================================================== */ static int clkgena_xable_pll(clk_t *clk_p, int enable) { unsigned long val; int bit, err = 0; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id != CLKA_PLL0HS && clk_p->id != CLKA_PLL1) return CLK_ERR_BAD_PARAMETER; bit = (clk_p->id == CLKA_PLL0HS ? 0 : 1); val = CLK_READ(CKGA_BASE_ADDRESS + CKGA_POWER_CFG); if (enable) val &= ~(1 << bit); else val |= (1 << bit); CLK_WRITE(CKGA_BASE_ADDRESS + CKGA_POWER_CFG, val); if (enable) err = clkgena_recalc(clk_p); else clk_p->rate = 0; return err; } /* ======================================================================== Name: clkgena_enable Description: Enable clock Returns: 'clk_err_t' error code ======================================================================== */ static int clkgena_enable(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (!clk_p->parent) /* Unsupported. Init must be called first. */ return CLK_ERR_BAD_PARAMETER; /* PLL power up */ if (clk_p->id >= CLKA_PLL0HS && clk_p->id <= CLKA_PLL1) return clkgena_xable_pll(clk_p, 1); err = clkgena_set_parent(clk_p, clk_p->parent); /* clkgena_set_parent() is performing also a recalc() */ return err; } /* ======================================================================== Name: clkgena_disable Description: Disable clock Returns: 'clk_err_t' error code ======================================================================== */ static int clkgena_disable(clk_t *clk_p) { unsigned long val; int idx, shift; unsigned long srcreg; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKA_PLL0HS || clk_p->id > CLKA_IC_IF_200) return CLK_ERR_BAD_PARAMETER; /* Can this clock be disabled ? */ if (clk_p->flags & CLK_ALWAYS_ENABLED) return 0; /* PLL power down */ if (clk_p->id >= CLKA_PLL0HS && clk_p->id <= CLKA_PLL1) return clkgena_xable_pll(clk_p, 0); idx = clkgena_get_index(clk_p->id, &srcreg, &shift); if (idx == -1) return CLK_ERR_BAD_PARAMETER; /* Disabling clock */ val = CLK_READ(CKGA_BASE_ADDRESS + srcreg) & ~(0x3 << shift); val = val | (3 << shift); /* 3 = STOP clock */ CLK_WRITE(CKGA_BASE_ADDRESS + srcreg, val); clk_p->rate = 0; return 0; } /* ======================================================================== Name: clkgena_set_div Description: Set divider ratio for clockgenA when possible ======================================================================== */ static int clkgena_set_div(clk_t *clk_p, unsigned long *div_p) { int idx; unsigned long div_cfg = 0; unsigned long srcreg, offset; int shift; if (!clk_p || !clk_p->parent) return CLK_ERR_BAD_PARAMETER; /* Computing divider config */ div_cfg = (*div_p - 1) & 0x1F; /* Which divider to setup ? */ idx = clkgena_get_index(clk_p->id, &srcreg, &shift); if (idx == -1) return CLK_ERR_BAD_PARAMETER; /* Now according to parent, let's write divider ratio */ offset = CKGA_SOURCE_CFG(clk_p->parent->id - CLKA_REF); CLK_WRITE(CKGA_BASE_ADDRESS + offset + (4 * idx), div_cfg); return 0; } /* ======================================================================== Name: clkgena_set_rate Description: Set clock frequency ======================================================================== */ static int clkgena_set_rate(clk_t *clk_p, unsigned long freq) { unsigned long div; int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if ((clk_p->id < CLKA_PLL0HS) || (clk_p->id > CLKA_IC_IF_200)) return CLK_ERR_BAD_PARAMETER; /* PLL set rate: to be completed */ if ((clk_p->id >= CLKA_PLL0HS) && (clk_p->id <= CLKA_PLL1)) return CLK_ERR_BAD_PARAMETER; /* We need a parent for these clocks */ if (!clk_p->parent) return CLK_ERR_INTERNAL; div = clk_p->parent->rate / freq; err = clkgena_set_div(clk_p, &div); if (!err) clk_p->rate = clk_p->parent->rate / div; return err; } /* ======================================================================== Name: clkgena_recalc Description: Get CKGA programmed clocks frequencies Returns: 0=NO error ======================================================================== */ static int clkgena_recalc(clk_t *clk_p) { unsigned long data, ratio; int idx; unsigned long srcreg, offset; int shift, err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (!clk_p->parent) return CLK_ERR_INTERNAL; /* Reading clock programmed value */ switch (clk_p->id) { case CLKA_REF: /* Clockgen A reference clock */ clk_p->rate = clk_p->parent->rate; break; case CLKA_PLL0HS: data = CLK_READ(CKGA_BASE_ADDRESS + CKGA_PLL0_CFG); err = clk_pll1600_get_rate(clk_p->parent->rate, data & 0x7, (data >> 8) & 0xff, &clk_p->rate); return err; case CLKA_PLL0LS: clk_p->rate = clk_p->parent->rate / 2; return 0; case CLKA_PLL1: data = CLK_READ(CKGA_BASE_ADDRESS + CKGA_PLL1_CFG); return clk_pll800_get_rate(clk_p->parent->rate, data & 0xff, (data >> 8) & 0xff, (data >> 16) & 0x7, &clk_p->rate); default: idx = clkgena_get_index(clk_p->id, &srcreg, &shift); if (idx == -1) return CLK_ERR_BAD_PARAMETER; /* Now according to source, let's get divider ratio */ offset = CKGA_SOURCE_CFG(clk_p->parent->id - CLKA_REF); data = CLK_READ(CKGA_BASE_ADDRESS + offset + (4 * idx)); ratio = (data & 0x1F) + 1; clk_p->rate = clk_p->parent->rate / ratio; break; } return 0; } /* ======================================================================== Name: clkgena_observe Description: allows to observe a clock on a SYSACLK_OUT Returns: 0=NO error ======================================================================== */ static int clkgena_observe(clk_t *clk_p, unsigned long *div_p) { unsigned long src = 0; unsigned long divcfg; /* WARNING: the obs_table[] must strictly follows clockgen enum order * taking into account any "holes" filled with 0xff */ static const unsigned long obs_table[] = { 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19 }; if (!clk_p || !div_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKA_IC_STNOC || clk_p->id > CLKA_IC_IF_200) return CLK_ERR_BAD_PARAMETER; src = obs_table[clk_p->id - CLKA_IC_STNOC]; if (src == 0xff) return 0; switch (*div_p) { case 2: divcfg = 0; break; case 4: divcfg = 1; break; default: divcfg = 2; *div_p = 1; break; } CLK_WRITE((CKGA_BASE_ADDRESS + CKGA_CLKOBS_MUX1_CFG), (divcfg << 6) | src); return 0; } /* ======================================================================== Name: clkgena_get_measure Description: Use internal HW feature (when avail.) to measure clock Returns: 'clk_err_t' error code. ======================================================================== */ static unsigned long clkgena_get_measure(clk_t *clk_p) { unsigned long src, data; unsigned long measure; /* WARNING: the measure_table[] must strictly follows clockgen enum * order taking into account any "holes" (CLKA_NOT_USED) filled with * 0xffffffff */ static const unsigned char measure_table[] = { 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19 }; if (!clk_p) return 0; if (clk_p->id < CLKA_IC_STNOC || clk_p->id > CLKA_IC_IF_200) return 0; src = measure_table[clk_p->id - CLKA_IC_STNOC]; if (src == 0xff) return 0; measure = 0; /* Loading the MAX Count 1000 in 30MHz Oscillator Counter */ CLK_WRITE(CKGA_BASE_ADDRESS + CKGA_CLKOBS_MASTER_MAXCOUNT, 0x3E8); CLK_WRITE(CKGA_BASE_ADDRESS + CKGA_CLKOBS_CMD, 3); /* Selecting clock to observe */ CLK_WRITE(CKGA_BASE_ADDRESS + CKGA_CLKOBS_MUX1_CFG, (1 << 7) | src); /* Start counting */ CLK_WRITE(CKGA_BASE_ADDRESS + CKGA_CLKOBS_CMD, 0); while (1) { mdelay(10); data = CLK_READ(CKGA_BASE_ADDRESS + CKGA_CLKOBS_STATUS); if (data & 1) break; /* IT */ } /* Reading value */ data = CLK_READ(CKGA_BASE_ADDRESS + CKGA_CLKOBS_SLAVE0_COUNT); measure = 30 * data * 1000; CLK_WRITE(CKGA_BASE_ADDRESS + CKGA_CLKOBS_CMD, 3); return measure; } /* ======================================================================== Name: clkgena_init Description: Read HW status to initialize 'clk_t' structure. Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgena_init(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; err = clkgena_identify_parent(clk_p); if (!err) err = clkgena_recalc(clk_p); return err; } /****************************************************************************** CLOCKGEN B ******************************************************************************/ static void clkgenb_unlock(void) { CLK_WRITE(CKGB_BASE_ADDRESS + CKGB_LOCK, 0xc0de); } static void clkgenb_lock(void) { CLK_WRITE(CKGB_BASE_ADDRESS + CKGB_LOCK, 0xc1a0); } /* ======================================================================== Name: clkgenb_xable_fsyn Description: enable/disable FSYN Returns: 0=NO error ======================================================================== */ static int clkgenb_xable_fsyn(clk_t *clk_p, unsigned long enable) { unsigned long val, clkout, ctrl, bit, ctrlval; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKB_FS0_CH1 || clk_p->id > CLKB_FS1_CH4) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKB_FS1_CH1) { clkout = CKGB_FS0_CLKOUT_CTRL; ctrl = CKGB_FS0_CTRL; } else { clkout = CKGB_FS1_CLKOUT_CTRL; ctrl = CKGB_FS1_CTRL; } clkgenb_unlock(); /* Powering down/up digital part */ val = CLK_READ(CKGB_BASE_ADDRESS + clkout); bit = (clk_p->id - CLKB_FS0_CH1) % 4; if (enable) val |= (1 << bit); else val &= ~(1 << bit); CLK_WRITE(CKGB_BASE_ADDRESS + clkout, val); /* Powering down/up analog part */ ctrlval = CLK_READ(CKGB_BASE_ADDRESS + ctrl); if (enable) ctrlval |= (1 << 4); else { /* If all channels are off then power down FSx */ if ((val & 0xf) == 0) ctrlval &= ~(1 << 4); } CLK_WRITE(CKGB_BASE_ADDRESS + ctrl, ctrlval); clkgenb_lock(); /* Freq recalc required only if a channel is enabled */ if (enable) return clkgenb_fsyn_recalc(clk_p); else clk_p->rate = 0; return 0; } /* ======================================================================== Name: clkgenb_xable_clock Description: Enable/disable clock (Clockgen B) Returns: 0=NO error ======================================================================== */ struct gen_utility { unsigned long clk_id; unsigned long info; }; static int clkgenb_xable_clock(clk_t *clk_p, unsigned long enable) { unsigned long bit, power; unsigned long i; static const struct gen_utility enable_clock[] = { {CLKB_DSS, 1}, {CLKB_PIX_HD, 3}, {CLKB_DISP_HD, 4}, {CLKB_TMDS_HDMI, 5}, {CLKB_656, 6}, {CLKB_GDP3, 7}, {CLKB_DISP_ID, 8}, {CLKB_PIX_SD, 9}, {CLKB_150, 11}, {CLKB_PP, 12}, {CLKB_LPC, 13} }; int err = 0; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKB_DVP) return 0; for (i = 0; i < ARRAY_SIZE(enable_clock); ++i) if (enable_clock[i].clk_id == clk_p->id) break; if (i == ARRAY_SIZE(enable_clock)) return CLK_ERR_BAD_PARAMETER; bit = enable_clock[i].info; power = CLK_READ(CKGB_BASE_ADDRESS + CKGB_POWER_ENABLE); clkgenb_unlock(); if (enable) power |= (1 << bit); else power &= ~(1 << bit); CLK_WRITE(CKGB_BASE_ADDRESS + CKGB_POWER_ENABLE, power); clkgenb_lock(); if (enable) err = clkgenb_recalc(clk_p); else clk_p->rate = 0; return err; } /* ======================================================================== Name: clkgenb_enable Description: enable clock or FSYN (clockgen B) Returns: O=NO error ======================================================================== */ static int clkgenb_enable(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id >= CLKB_FS0_CH1 && clk_p->id <= CLKB_FS1_CH4) err = clkgenb_xable_fsyn(clk_p, 1); else err = clkgenb_xable_clock(clk_p, 1); return err; } /* ======================================================================== Name: clkgenb_disable Description: Disable clock Returns: O=NO error ======================================================================== */ static int clkgenb_disable(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id >= CLKB_FS0_CH1 && clk_p->id <= CLKB_FS1_CH4) err = clkgenb_xable_fsyn(clk_p, 0); else err = clkgenb_xable_clock(clk_p, 0); return err; } /* ======================================================================== Name: clkgenb_set_parent Description: Set clock source for clockgenB when possible Returns: 0=NO error ======================================================================== */ static int clkgenb_set_parent(clk_t *clk_p, clk_t *parent_p) { unsigned long set = 0; /* Each bit set to 1 will be SETTED */ unsigned long reset = 0; /* Each bit set to 1 will be RESETTED */ unsigned long reg; /* Register address */ unsigned long val; if (!clk_p || !parent_p) return CLK_ERR_BAD_PARAMETER; switch (clk_p->id) { case CLKB_REF: switch (parent_p->id) { case CLK_SATA_OSC: reset = 1; break; case CLK_SYSALT: set = 1; break; } reg = CKGB_BASE_ADDRESS + CKGB_CRISTAL_SEL; break; case CLKB_PIX_HD: if (parent_p->id == CLKB_FS0_CH1) reset = 1 << 14; else set = 1 << 14; reg = CKGB_BASE_ADDRESS + CKGB_DISPLAY_CFG; break; case CLKB_GDP3: if ((parent_p->id == CLKB_DISP_HD) || (parent_p->id == CLKB_FS0_CH1)) reset = 1 << 0; else set = 1 << 0; reg = CKGB_BASE_ADDRESS + CKGB_FS_SELECT; break; case CLKB_DVP: if ((parent_p->id != CLKB_FS0_CH1) && (parent_p->id != CLKB_FS1_CH1) && (parent_p->id != CLKB_PIX_FROM_DVP)) return CLK_ERR_BAD_PARAMETER; if (parent_p->id == CLKB_FS0_CH1) { set = 1 << 3; reset = 1 << 2; } else if (parent_p->id == CLKB_FS1_CH1) { set = 0x3 << 2; } else reset = 1 << 3; reg = CKGB_BASE_ADDRESS + CKGB_FS_SELECT; break; case CLKB_PIX_SD: if (parent_p->id == CLKB_FS0_CH1) reset = 1 << 1; else set = 1 << 1; reg = CKGB_BASE_ADDRESS + CKGB_FS_SELECT; break; case CLKB_PIP: /* In fact NOT a clockgen B clock but closely linked to it */ if (parent_p->id == CLKB_DISP_ID) val = 0; else if (parent_p->id == CLKB_DISP_HD) val = 1; else return CLK_ERR_BAD_PARAMETER; SYSCONF_WRITE(SYS_CFG, 6, 0, 0, val); clk_p->parent = parent_p; /* Special case since config done thru sys_conf register */ return 0; default: return CLK_ERR_BAD_PARAMETER; } clkgenb_unlock(); val = CLK_READ(reg); val = val & ~reset; val = val | set; CLK_WRITE(reg, val); clkgenb_lock(); clk_p->parent = parent_p; return clkgenb_recalc(clk_p); } /* ======================================================================== Name: clkgenb_set_rate Description: Set clock frequency ======================================================================== */ static int clkgenb_set_rate(clk_t *clk_p, unsigned long freq) { unsigned long div; int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (!clk_p->parent) /* A parent is expected to these clocks */ return CLK_ERR_INTERNAL; if ((clk_p->id >= CLKB_FS0_CH1) && (clk_p->id <= CLKB_FS1_CH4)) /* clkgenb_set_fsclock() is updating clk_p->rate */ return clkgenb_set_fsclock(clk_p, freq); div = clk_p->parent->rate / freq + (((freq/2) > (clk_p->parent->rate % freq))?0:1); err = clkgenb_set_div(clk_p, &div); if (!err) clk_p->rate = freq; return err; } /* ======================================================================== Name: clkgenb_set_fsclock Description: Set FS clock Returns: 0=NO error ======================================================================== */ static int clkgenb_set_fsclock(clk_t *clk_p, unsigned long freq) { unsigned long md, pe, sdiv; int bank, channel; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (!clk_p->parent) return CLK_ERR_INTERNAL; if (clk_p->id < CLKB_FS0_CH1 || clk_p->id > CLKB_FS1_CH4) return CLK_ERR_BAD_PARAMETER; /* Computing FSyn params. Should be common function with FSyn type */ if (clk_fsyn_get_params(clk_p->parent->rate, freq, &md, &pe, &sdiv)) return CLK_ERR_BAD_PARAMETER; bank = (clk_p->id - CLKB_FS0_CH1) / 4; channel = (clk_p->id - CLKB_FS0_CH1) % 4; clkgenb_unlock(); CLK_WRITE(CKGB_BASE_ADDRESS + CKGB_FS_MD(bank, channel), md); CLK_WRITE(CKGB_BASE_ADDRESS + CKGB_FS_PE(bank, channel), pe); CLK_WRITE(CKGB_BASE_ADDRESS + CKGB_FS_SDIV(bank, channel), sdiv); CLK_WRITE(CKGB_BASE_ADDRESS + CKGB_FS_EN_PRG(bank, channel), 0x1); clkgenb_lock(); clk_p->rate = freq; return 0; } /* ======================================================================== Name: clkgenb_set_div Description: Set divider ratio for clockgenB when possible Returns: 0=NO error ======================================================================== */ static int clkgenb_set_div(clk_t *clk_p, unsigned long *div_p) { unsigned long set = 0; /* Each bit set to 1 will be SETTED */ unsigned long reset = 0; /* Each bit set to 1 will be RESETTED */ unsigned long shift = 0; unsigned long reg; unsigned long val; static const unsigned char clk_shift[] = { 0, /* CLKB_TMDS_HDMI */ 0xff, /* CLKB_PIX_HD */ 4, /* CLKB_DISP_HD */ 6, /* CLKB_656 */ 0xff, /* CLKB_GDP3 */ 8, /* CLKB_DISP_ID */ 10, /* CLKB_DVP */ 10, /* CLKB_PIX_SD */ }; static const unsigned char clk_shift_pwd[] = { 1, /* CLKB_TMDS_HDMI */ 0xff, /* CLKB_PIX_HD */ 4, /* CLKB_DISP_HD */ 5, /* CLKB_656 */ 0xff, /* CLKB_GDP3 */ 6, /* CLKB_DISP_ID */ 7, /* CLKB_PIX_SD */ }; static const unsigned char divisor_value[] = { /* 1, 2, 3, 4, 5, 6, 7, 8 */ 3, 0, 0xff, 1, 0xff, 0xff, 0xff, 2 }; if (!clk_p) return CLK_ERR_BAD_PARAMETER; /* the hw support specific divisor factor therefore * reject immediatelly a wrong divisor */ if (*div_p < 1 || (*div_p > 8 && *div_p != 1024)) return CLK_ERR_BAD_PARAMETER; if (*div_p == 1024) { shift = clk_shift_pwd[clk_p->id - CLKB_TMDS_HDMI]; reg = CKGB_POWER_DOWN; reset = 1; set = 1; } else { set = divisor_value[*div_p - 1]; if (set == 0xff) return CLK_ERR_BAD_PARAMETER; shift = clk_shift[clk_p->id - CLKB_TMDS_HDMI]; reset = 3; reg = CKGB_DISPLAY_CFG; } if (shift == 0xff) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKB_PIX_SD && clk_p->parent->id == CLKB_FS1_CH1) shift += 2; val = CLK_READ(CKGB_BASE_ADDRESS + reg); val = val & ~(reset << shift); val = val | (set << shift); clkgenb_unlock(); CLK_WRITE(CKGB_BASE_ADDRESS + reg, val); clkgenb_lock(); return 0; } /* ======================================================================== Name: clkgenb_observe Description: Allows to observe a clock on a PIO5_2 Returns: 0=NO error ======================================================================== */ static int clkgenb_observe(clk_t *clk_p, unsigned long *div_p) { static const unsigned long observe_table[] = { 1, /* CLKB_TMDS_HDMI */ 3, /* CLKB_PIX_HD */ 4, /* CLKB_DISP_HD */ 5, /* CLKB_656 */ 6, /* CLKB_GDP3 */ 7, /* CLKB_DISP_ID */ 8, /* CLKB_PIX_SD */ 12,/* CLKB_PP */ -1,/* CLKB_150 */ 13,/* CLKB_LPC */ 9, /* CLKB_DSS */ -1 /* CLKB_PIP */}; unsigned long out0, out1 = 0; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKB_PIP || clk_p->id == CLKB_150) return CLK_ERR_BAD_PARAMETER; out0 = observe_table[clk_p->id - CLKB_TMDS_HDMI]; if (clk_p->id == CLKB_PP) out1 = 11; clkgenb_unlock(); CLK_WRITE((CKGB_BASE_ADDRESS + CKGB_OUT_CTRL), (out1 << 4) | out0); clkgenb_lock(); /* Set PIO5_2 for observation (alternate function output mode) */ PIO_SET_MODE(5, 2, STPIO_ALT_OUT); /* No possible predivider on clockgen B */ *div_p = 1; return 0; } /* ======================================================================== Name: clkgenb_fsyn_recalc Description: Check FSYN & channels status... active, disabled, standby 'clk_p->rate' is updated accordingly. Returns: Error code. ======================================================================== */ static int clkgenb_fsyn_recalc(clk_t *clk_p) { unsigned long val, clkout, ctrl, bit; unsigned long pe, md, sdiv; int bank, channel; if (!clk_p || !clk_p->parent) return CLK_ERR_BAD_PARAMETER; /* Which FSYN control registers to use ? */ switch (clk_p->id) { case CLKB_FS0_CH1 ... CLKB_FS0_CH4: clkout = CKGB_FS0_CLKOUT_CTRL; ctrl = CKGB_FS0_CTRL; break; case CLKB_FS1_CH1 ... CLKB_FS1_CH4: clkout = CKGB_FS1_CLKOUT_CTRL; ctrl = CKGB_FS1_CTRL; break; default: return CLK_ERR_BAD_PARAMETER; } /* Is FSYN analog part UP ? */ val = CLK_READ(CKGB_BASE_ADDRESS + ctrl); if ((val & (1 << 4)) == 0) { /* NO. Analog part is powered down */ clk_p->rate = 0; return 0; } /* Is FSYN digital part UP ? */ bit = (clk_p->id - CLKB_FS0_CH1) % 4; val = CLK_READ(CKGB_BASE_ADDRESS + clkout); if ((val & (1 << bit)) == 0) { /* Digital standby */ clk_p->rate = 0; return 0; } /* FSYN is up and running. Now computing frequency */ bank = (clk_p->id - CLKB_FS0_CH1) / 4; channel = (clk_p->id - CLKB_FS0_CH1) % 4; pe = CLK_READ(CKGB_BASE_ADDRESS + CKGB_FS_PE(bank, channel)); md = CLK_READ(CKGB_BASE_ADDRESS + CKGB_FS_MD(bank, channel)); sdiv = CLK_READ(CKGB_BASE_ADDRESS + CKGB_FS_SDIV(bank, channel)); return clk_fsyn_get_rate(clk_p->parent->rate, pe, md, sdiv, &clk_p->rate); } /* ======================================================================== Name: clkgenb_recalc Description: Get CKGB clocks frequencies function Returns: 0=NO error ======================================================================== */ /* Check clock enable value for clockgen B. Returns: 1=RUNNING, 0=DISABLED */ static int clkgenb_is_running(unsigned long power, int bit) { if (power & (1 << bit)) return 1; return 0; } static int clkgenb_recalc(clk_t *clk_p) { unsigned long displaycfg, powerdown, fs_sel, power_en; static const unsigned char tab2481[] = { 2, 4, 8, 1 }; static const unsigned char tab2482[] = { 2, 4, 8, 2 }; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (!clk_p->parent) return CLK_ERR_INTERNAL; /* parent_p clock is unknown */ /* Read mux */ displaycfg = CLK_READ(CKGB_BASE_ADDRESS + CKGB_DISPLAY_CFG); powerdown = CLK_READ(CKGB_BASE_ADDRESS + CKGB_POWER_DOWN); fs_sel = CLK_READ(CKGB_BASE_ADDRESS + CKGB_FS_SELECT); power_en = CLK_READ(CKGB_BASE_ADDRESS + CKGB_POWER_ENABLE); switch (clk_p->id) { case CLKB_REF: clk_p->rate = clk_p->parent->rate; break; case CLKB_FS0_CH1 ... CLKB_FS1_CH4: return clkgenb_fsyn_recalc(clk_p); case CLKB_TMDS_HDMI: /* tmds_hdmi_clk */ if (powerdown & (1 << 1)) { clk_p->rate = clk_p->parent->rate / 1024; } else { switch (displaycfg & 0x3) { case 0: clk_p->rate =clk_p->parent->rate/2; break; case 3: clk_p->rate = clk_p->parent->rate ; break; case 1: clk_p->rate = clk_p->parent->rate / 4; break; } } if (!clkgenb_is_running(power_en, 5)) clk_p->rate = 0; break; case CLKB_PIX_HD: /* pix_hd */ if (displaycfg & (1 << 14)) /* pix_hd source = FSYN1 */ clk_p->rate = clk_p->parent->rate; else /* pix_hd source = FSYN0 */ clk_p->rate = clk_p->parent->rate; if (powerdown & (1 << 3)) clk_p->rate = clk_p->rate / 1024; if (!clkgenb_is_running(power_en, 3)) clk_p->rate = 0; break; case CLKB_DISP_HD: /* disp_hd */ if (powerdown & (1 << 4)) clk_p->rate = clk_p->parent->rate / 1024; else clk_p->rate = clk_p->parent->rate / tab2482[(displaycfg >> 4) & 0x3]; if (!clkgenb_is_running(power_en, 4)) clk_p->rate = 0; break; case CLKB_656: /* ccir656_clk */ if (powerdown & (1<<5)) clk_p->rate = clk_p->parent->rate / 1024; else { switch ((displaycfg >> 6) & 0x3) { case 0: clk_p->rate = clk_p->parent->rate/2; break; case 3: clk_p->rate = clk_p->parent->rate; break; case 1: clk_p->rate = clk_p->parent->rate / 4; break; } } if (!clkgenb_is_running(power_en, 6)) clk_p->rate = 0; break; case CLKB_DISP_ID: /* disp_id */ if (powerdown & (1 << 6)) clk_p->rate = clk_p->parent->rate / 1024; else { switch ((displaycfg >> 8) & 0x3) { case 0: case 3: clk_p->rate = clk_p->parent->rate / 2; break; } } if (!clkgenb_is_running(power_en, 8)) clk_p->rate = 0; break; case CLKB_PIX_SD: /* pix_sd */ /* source is FS0 */ if (powerdown & (1<<7)) clk_p->rate = clk_p->parent->rate / 1024; else { switch ((displaycfg >> 10) & 0x3) { case 1: clk_p->rate = clk_p->parent->rate / 4; break; } } if (!clkgenb_is_running(power_en, 9)) clk_p->rate = 0; break; case CLKB_GDP3: /* gdp3_clk */ if (fs_sel & 0x1) /* source is FS1 */ clk_p->rate = clk_p->parent->rate; else /* source is FS0 */ clk_p->rate = clk_p->parent->rate; if (!clkgenb_is_running(power_en, 7)) clk_p->rate = 0; break; case CLKB_DVP: /* CKGB_DVP */ switch (clk_p->parent->id) { case CLKB_FS0_CH1: clk_p->rate = clk_p->parent->rate / tab2482[(displaycfg >> 10) & 0x3]; break; case CLKB_FS1_CH1: clk_p->rate = clk_p->parent->rate / tab2481[(displaycfg >> 12) & 0x3]; break; default: /* pix from pad. Don't have any value */ break; } break; case CLKB_DSS: clk_p->rate = clk_p->parent->rate; if (!clkgenb_is_running(power_en, 0)) clk_p->rate = 0; break; case CLKB_PP: clk_p->rate = clk_p->parent->rate; if (!clkgenb_is_running(power_en, 12)) clk_p->rate = 0; break; case CLKB_LPC: clk_p->rate = clk_p->parent->rate / 1024; if (!clkgenb_is_running(power_en, 13)) clk_p->rate = 0; break; case CLKB_PIX_FROM_DVP: clk_p->rate = clk_p->parent->rate; break; default: return CLK_ERR_BAD_PARAMETER; } return 0; } /* ======================================================================== Name: clkgenb_identify_parent Description: Identify parent clock Returns: clk_err_t ======================================================================== */ static int clkgenb_identify_parent(clk_t *clk_p) { unsigned long sel, fs_sel; unsigned long displaycfg; const clk_t *fs_clk[2] = { &clk_clocks[CLKB_FS0_CH1], &clk_clocks[CLKB_FS1_CH1] }; const clk_t *dvp_fs_clock[4] = { &clk_clocks[CLKB_PIX_FROM_DVP], &clk_clocks[CLKB_PIX_FROM_DVP], &clk_clocks[CLKB_FS0_CH1], &clk_clocks[CLKB_FS1_CH1] }; int p_id; if (!clk_p) return CLK_ERR_BAD_PARAMETER; fs_sel = CLK_READ(CKGB_BASE_ADDRESS + CKGB_FS_SELECT); switch (clk_p->id) { case CLKB_REF: /* What is clockgen B ref clock ? */ sel = CLK_READ(CKGB_BASE_ADDRESS + CKGB_CRISTAL_SEL); clk_p->parent = &clk_clocks[CLK_SATA_OSC + (sel & 0x1)]; break; case CLKB_PIX_HD: /* pix_hd */ displaycfg = CLK_READ(CKGB_BASE_ADDRESS + CKGB_DISPLAY_CFG); p_id = ((displaycfg & (1 << 14)) ? 1 : 0); clk_p->parent = fs_clk[p_id]; break; case CLKB_PIX_SD: /* pix_sd */ p_id = ((fs_sel & 0x2) ? 1 : 0); clk_p->parent = fs_clk[p_id]; break; case CLKB_GDP3: /* gdp3_clk */ p_id = ((fs_sel & 0x1) ? 1 : 0); clk_p->parent = fs_clk[p_id]; break; case CLKB_DVP: /* CKGB_DVP */ clk_p->parent = dvp_fs_clock[(fs_sel >> 2) & 0x3]; break; } /* Other clockgen B clocks are statically initialized thanks to _CLK_P() macro */ return 0; } /* ======================================================================== Name: clkgenb_init Description: Read HW status to initialize 'clk_t' structure. Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenb_init(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; err = clkgenb_identify_parent(clk_p); if (!err) err = clkgenb_recalc(clk_p); return err; } /****************************************************************************** CLOCKGEN C (audio) ******************************************************************************/ /* ======================================================================== Name: clkgenc_fsyn_recalc Description: Get CKGC FSYN clocks frequencies function Returns: 0=NO error ======================================================================== */ static int clkgenc_fsyn_recalc(clk_t *clk_p) { unsigned long cfg, dig_bit, en_bit; unsigned long pe, md, sdiv; int channel; int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKC_FS0_CH1 || clk_p->id > CLKC_FS0_CH4) return CLK_ERR_BAD_PARAMETER; /* Is FSYN analog UP ? */ cfg = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0)); if (!(cfg & (1 << 14))) { /* Analog power down */ clk_p->rate = 0; return 0; } /* Is FSYN digital part UP & enabled ? */ dig_bit = 10 + (clk_p->id - CLKC_FS0_CH1); en_bit = 6 + (clk_p->id - CLKC_FS0_CH1); if ((cfg & (1 << dig_bit)) == 0) { /* digital part in standby */ clk_p->rate = 0; return 0; } if ((cfg & (1 << en_bit)) == 0) { /* disabled */ clk_p->rate = 0; return 0; } /* FSYN up & running. Computing frequency */ channel = (clk_p->id - CLKC_FS0_CH1) % 4; pe = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_PE(0, channel)); md = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_MD(0, channel)); sdiv = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_SDIV(0, channel)); err = clk_fsyn_get_rate(clk_p->parent->rate, pe, md, sdiv, &clk_p->rate); return err; } /* ======================================================================== Name: clkgenc_recalc Description: Get CKGC clocks frequencies function Returns: 0=NO error ======================================================================== */ static int clkgenc_recalc(clk_t *clk_p) { if (!clk_p) return CLK_ERR_BAD_PARAMETER; switch (clk_p->id) { case CLKC_REF: clk_p->rate = clk_p->parent->rate; break; case CLKC_FS0_CH1 ... CLKC_FS0_CH4: /* FS0 clocks */ return clkgenc_fsyn_recalc(clk_p); default: return CLK_ERR_BAD_PARAMETER; } return 0; } /* ======================================================================== Name: clkgenc_set_rate Description: Set CKGC clocks frequencies Returns: 0=NO error ======================================================================== */ static int clkgenc_set_rate(clk_t *clk_p, unsigned long freq) { unsigned long md, pe, sdiv; unsigned long reg_value = 0; int channel; static const unsigned long set_rate_table[] = {0x06, 0x0A, 0x12, 0x22}; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKC_FS0_CH1 || clk_p->id > CLKC_FS0_CH4) return CLK_ERR_BAD_PARAMETER; /* Computing FSyn params. Should be common function with FSyn type */ if (clk_fsyn_get_params(clk_p->parent->rate, freq, &md, &pe, &sdiv)) return CLK_ERR_BAD_PARAMETER; reg_value = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0)); reg_value |= set_rate_table[clk_p->id - CLKC_FS0_CH1]; /* Select FS clock only for the clock specified */ CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0), reg_value); channel = (clk_p->id - CLKC_FS0_CH1) % 4; CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_PE(0, channel), pe); CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_MD(0, channel), md); CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_SDIV(0, channel), sdiv); CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_EN_PRG(0, channel), 0x01); CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_EN_PRG(0, channel), 0x00); return clkgenc_recalc(clk_p); } /* ======================================================================== Name: clkgenc_identify_parent Description: Identify parent clock Returns: clk_err_t ======================================================================== */ static int clkgenc_identify_parent(clk_t *clk_p) { unsigned long sel; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKC_REF) { sel = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0)) >> 23; clk_p->parent = &clk_clocks[CLK_SATA_OSC + (sel & 0x1)]; } /* Note: other clocks are set statically */ return 0; } /* ======================================================================== Name: clkgenc_set_parent Description: Set parent clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenc_set_parent(clk_t *clk_p, clk_t *parent_p) { unsigned long sel, data; if (!clk_p || !parent_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id != CLKC_REF) return CLK_ERR_BAD_PARAMETER; switch (parent_p->id) { case CLK_SATA_OSC: sel = 0; break; case CLK_SYSALT: sel = 1; break; default: return CLK_ERR_BAD_PARAMETER; } data = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0)) & ~(0x3 << 23); CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0), data | (sel << 23)); clk_p->parent = parent_p; clk_p->rate = clk_p->parent->rate; return 0; } /* ======================================================================== Name: clkgenc_init Description: Read HW status to initialize 'clk_t' structure. Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenc_init(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; err = clkgenc_identify_parent(clk_p); if (!err) err = clkgenc_recalc(clk_p); return err; } /* ======================================================================== Name: clkgenc_xable_fsyn Description: Enable/Disable FSYN. If all channels OFF, FSYN is powered down. Returns: 'clk_err_t' error code ======================================================================== */ static int clkgenc_xable_fsyn(clk_t *clk_p, unsigned long enable) { unsigned long val; /* Digital standby bits table. Warning: enum order: CLKC_FS0_CH1 ... CLKC_FS0_CH4 */ static const unsigned long dig_bit[] = {10, 11, 12, 13}; static const unsigned long en_bit[] = {6, 7, 8, 9}; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKC_FS0_CH1 || clk_p->id > CLKC_FS0_CH4) return CLK_ERR_BAD_PARAMETER; val = CLK_READ(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0)); /* Powering down/up digital part */ if (clk_p->id >= CLKC_FS0_CH1 && clk_p->id <= CLKC_FS0_CH4) { if (enable) { val |= (1 << dig_bit[clk_p->id - CLKC_FS0_CH1]); val |= (1 << en_bit[clk_p->id - CLKC_FS0_CH1]); } else { val &= ~(1 << dig_bit[clk_p->id - CLKC_FS0_CH1]); val &= ~(1 << en_bit[clk_p->id - CLKC_FS0_CH1]); } } /* Powering down/up analog part */ if (enable) val |= (1 << 14); else { /* If all channels are off then power down FS0 */ if ((val & 0x3fc0) == 0) val &= ~(1 << 14); } CLK_WRITE(CKGC_BASE_ADDRESS + CKGC_FS_CFG(0), val); /* Freq recalc required only if a channel is enabled */ if (enable) return clkgenc_fsyn_recalc(clk_p); else clk_p->rate = 0; return 0; } /* ======================================================================== Name: clkgenc_enable Description: Enable clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenc_enable(clk_t *clk_p) { return clkgenc_xable_fsyn(clk_p, 1); } /* ======================================================================== Name: clkgenc_disable Description: Disable clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenc_disable(clk_t *clk_p) { return clkgenc_xable_fsyn(clk_p, 0); } /****************************************************************************** CLOCKGEN D (LMI) ******************************************************************************/ /* ======================================================================== Name: clkgend_recalc Description: Get CKGD (LMI) clocks frequencies (in Hz) Returns: 0=NO error ======================================================================== */ static int clkgend_recalc(clk_t *clk_p) { unsigned long rdiv, ddiv; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKD_REF) clk_p->rate = clk_p->parent->rate; else if (clk_p->id == CLKD_LMI2X) { rdiv = SYSCONF_READ(SYS_CFG, 11, 9, 11); ddiv = SYSCONF_READ(SYS_CFG, 11, 1, 8); clk_p->rate = (((clk_p->parent->rate / 1000000) * ddiv) / rdiv) * 1000000; } else return CLK_ERR_BAD_PARAMETER; /* Unknown clock */ return 0; } /* ======================================================================== Name: clkgend_identify_parent Description: Identify parent clock Returns: clk_err_t ======================================================================== */ static int clkgend_identify_parent(clk_t *clk_p) { unsigned long sel; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKD_REF) { sel = SYSCONF_READ(SYS_CFG, 40, 0, 0); clk_p->parent = &clk_clocks[CLK_SATA_OSC + sel]; } return 0; } /* ======================================================================== Name: clkgend_set_parent Description: Set parent clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgend_set_parent(clk_t *clk_p, clk_t *parent_p) { unsigned long sel; if (!clk_p || !parent_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id != CLKD_REF) return CLK_ERR_BAD_PARAMETER; switch (parent_p->id) { case CLK_SATA_OSC: case CLK_SYSALT: sel = parent_p->id - CLK_SATA_OSC; break; default: return CLK_ERR_BAD_PARAMETER; } SYSCONF_WRITE(SYS_CFG, 40, 0, 0, sel); clk_p->parent = parent_p; return clkgend_recalc(clk_p); } /* ======================================================================== Name: clkgend_init Description: Read HW status to initialize 'clk_t' structure. Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgend_init(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; err = clkgend_identify_parent(clk_p); if (!err) err = clkgend_recalc(clk_p); return err; } /****************************************************************************** CLOCKGEN E (Cable card/0498) ******************************************************************************/ /* ======================================================================== Name: clkgene_recalc Description: Get CKGE (USB) clocks frequencies (in Hz) Returns: 0=NO error ======================================================================== */ static int clkgene_recalc(clk_t *clk_p) { unsigned long val; const int stby_bit[] = { -1, 0, 1, 2, -1, 3, 4, 5, 5 }; const int div_bit[] = { 3, 3, 4, 4, 1, 3, 2, 5, 6, 0 }; int bit; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKE_QAM || clk_p->id > CLKE_TXCLK) return CLK_ERR_BAD_PARAMETER; /* Is in standby mode ? */ val = SYSCONF_READ(SYS_CFG, 36, 0, 5); bit = stby_bit[clk_p->id - CLKE_QAM]; if (bit != -1) { if (val & (1 << bit)) { clk_p->rate = 0; return 0; } } /* Computing div ratio */ val = SYSCONF_READ(SYS_CFG, 36, 6, 12); bit = div_bit[clk_p->id - CLKE_QAM]; if (bit == 3 || bit == 4) { if (val & (1 << bit)) clk_p->rate = clk_p->parent->rate / 2; else clk_p->rate = clk_p->parent->rate; } else if (bit == 6) { if (val & (1 << bit)) clk_p->rate = clk_p->parent->rate / 4; else clk_p->rate = clk_p->parent->rate; } else clk_p->rate = clk_p->parent->rate; return 0; } /* ======================================================================== Name: clkgene_xable_clock Description: Enable/disble clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgene_xable_clock(clk_t *clk_p, int enable) { const int stby_bit[] = { -1, 0, 1, 2, -1, 3, 4, 5, 5 }; unsigned long val; int bit; if (!clk_p || clk_p->id < CLKE_QAM || clk_p->id > CLKE_TXCLK) return CLK_ERR_BAD_PARAMETER; bit = stby_bit[clk_p->id - CLKE_QAM]; if (bit == -1) return CLK_ERR_BAD_PARAMETER; /* Now enabling/disabling */ val = SYSCONF_READ(SYS_CFG, 36, 0, 5); if (enable) val &= ~(1 << bit); else val |= 1 << bit; SYSCONF_WRITE(SYS_CFG, 36, 0, 5, val); /* Updating rate */ if (enable) return clkgene_recalc(clk_p); clk_p->rate = 0; return 0; } /* ======================================================================== Name: clkgene_enable Description: Enable clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgene_enable(clk_t *clk_p) { return clkgene_xable_clock(clk_p, 1); } /* ======================================================================== Name: clkgene_disable Description: Disable clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgene_disable(clk_t *clk_p) { return clkgene_xable_clock(clk_p, 0); } /* ======================================================================== Name: clkgene_init Description: Read HW status to initialize 'clk_t' structure. Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgene_init(clk_t *clk_p) { int err = 0; if (!clk_p || clk_p->id < CLKE_REF || clk_p->id > CLKE_TXCLK) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKE_REF) { clk_p->parent = NULL; clk_p->rate = IFE_MCLK * 1000000; } else err = clkgene_recalc(clk_p); return err; } /****************************************************************************** CLOCKGEN F (USB2.0 controller) ******************************************************************************/ /* ======================================================================== Name: clkgenf_recalc Description: Get CKGE (USB) clocks frequencies (in Hz) Returns: 0=NO error ======================================================================== */ static int clkgenf_recalc(clk_t *clk_p) { unsigned long val; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKF_REF) clk_p->rate = clk_p->parent->rate; else if (clk_p->id == CLKF_USB_PHY) clk_p->rate = 60000000; else if (clk_p->id == CLKF_USB48) { if (chip_major_version() < 2) { if (SYSCONF_READ(SYS_CFG, 4, 5, 5)) clk_p->rate = 48000000; else clk_p->rate = 0; } else { val = SYSCONF_READ(SYS_CFG, 4, 4, 5); if (val >= 2) clk_p->rate = 48000000; else if (val == 0) clk_p->rate = 0; else { /* To be completed. The clock is driven * from clockgen B */ } } } return 0; } /* ======================================================================== Name: clkgenf_set_parent Description: Change parent clock Returns: Pointer on parent 'clk_t' structure, or NULL (none or error) ======================================================================== */ static int clkgenf_set_parent(clk_t *clk_p, clk_t *parent_p) { unsigned long sel; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id != CLKF_REF) return CLK_ERR_BAD_PARAMETER; switch (parent_p->id) { case CLK_SATA_OSC: case CLK_SYSALT: sel = parent_p->id - CLK_SATA_OSC; break; default: return CLK_ERR_BAD_PARAMETER; } SYSCONF_WRITE(SYS_CFG, 40, 2, 2, sel); clk_p->parent = parent_p; return 0; } /* ======================================================================== Name: clkgenf_identify_parent Description: Identify parent clock Returns: clk_err_t ======================================================================== */ static int clkgenf_identify_parent(clk_t *clk_p) { unsigned long sel; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id == CLKF_REF) { sel = SYSCONF_READ(SYS_CFG, 40, 2, 2); clk_p->parent = &clk_clocks[CLK_SATA_OSC + sel]; } else if (clk_p->id == CLKF_USB48 && chip_major_version() >= 2) { if (SYSCONF_READ(SYS_CFG, 4, 4, 5) == 1) clk_p->parent = &clk_clocks[CLKB_150]; else clk_p->parent = &clk_clocks[CLKF_REF]; } return 0; } /* ======================================================================== Name: clkgenf_init Description: Read HW status to initialize 'clk_t' structure. Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenf_init(clk_t *clk_p) { int err; if (!clk_p) return CLK_ERR_BAD_PARAMETER; if (clk_p->id < CLKF_REF || clk_p->id > CLKF_USB48) return CLK_ERR_BAD_PARAMETER; err = clkgenf_identify_parent(clk_p); if (!err) err = clkgenf_recalc(clk_p); return err; } /* ======================================================================== Name: clkgenf_enable Description: Enable clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenf_enable(clk_t *clk_p) { if (!clk_p || clk_p->id != CLKF_USB48) return CLK_ERR_BAD_PARAMETER; if (chip_major_version() < 2) SYSCONF_WRITE(SYS_CFG, 4, 5, 5, 1); else { SYSCONF_WRITE(SYS_CFG, 4, 10, 10, 1); /* USB_XTAL_VALID */ SYSCONF_WRITE(SYS_CFG, 4, 4, 5, 3); } clk_p->rate = 60000000; return 0; } /* ======================================================================== Name: clkgenf_disable Description: Disable clock Returns: 'clk_err_t' error code. ======================================================================== */ static int clkgenf_disable(clk_t *clk_p) { if (!clk_p || clk_p->id != CLKF_USB48) return CLK_ERR_BAD_PARAMETER; if (chip_major_version() < 2) SYSCONF_WRITE(SYS_CFG, 4, 5, 5, 0); else SYSCONF_WRITE(SYS_CFG, 4, 4, 5, 0); clk_p->rate = 0; return 0; }