Tom Rini | 83d290c | 2018-05-06 17:58:06 -0400 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
Peng Fan | d0f8516 | 2017-02-22 16:21:42 +0800 | [diff] [blame] | 2 | /* |
| 3 | * Copyright (C) 2016 Freescale Semiconductor, Inc. |
Peng Fan | d0f8516 | 2017-02-22 16:21:42 +0800 | [diff] [blame] | 4 | */ |
| 5 | |
| 6 | #include <common.h> |
| 7 | #include <div64.h> |
| 8 | #include <asm/io.h> |
| 9 | #include <errno.h> |
| 10 | #include <asm/arch/imx-regs.h> |
| 11 | #include <asm/arch/pcc.h> |
| 12 | #include <asm/arch/sys_proto.h> |
| 13 | |
Peng Fan | d0f8516 | 2017-02-22 16:21:42 +0800 | [diff] [blame] | 14 | #define PCC_CLKSRC_TYPES 2 |
| 15 | #define PCC_CLKSRC_NUM 7 |
| 16 | |
| 17 | static enum scg_clk pcc_clksrc[PCC_CLKSRC_TYPES][PCC_CLKSRC_NUM] = { |
| 18 | { SCG_NIC1_BUS_CLK, |
| 19 | SCG_NIC1_CLK, |
| 20 | SCG_DDR_CLK, |
| 21 | SCG_APLL_PFD2_CLK, |
| 22 | SCG_APLL_PFD1_CLK, |
| 23 | SCG_APLL_PFD0_CLK, |
| 24 | USB_PLL_OUT, |
| 25 | }, |
| 26 | { SCG_SOSC_DIV2_CLK, /* SOSC BUS clock */ |
| 27 | MIPI_PLL_OUT, |
| 28 | SCG_FIRC_DIV2_CLK, /* FIRC BUS clock */ |
| 29 | SCG_ROSC_CLK, |
| 30 | SCG_NIC1_BUS_CLK, |
| 31 | SCG_NIC1_CLK, |
| 32 | SCG_APLL_PFD3_CLK, |
| 33 | }, |
| 34 | }; |
| 35 | |
| 36 | static struct pcc_entry pcc_arrays[] = { |
| 37 | {PCC2_RBASE, DMA1_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 38 | {PCC2_RBASE, RGPIO1_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 39 | {PCC2_RBASE, FLEXBUS0_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 40 | {PCC2_RBASE, SEMA42_1_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 41 | {PCC2_RBASE, DMA1_CH_MUX0_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 42 | {PCC2_RBASE, SNVS_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 43 | {PCC2_RBASE, CAAM_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 44 | {PCC2_RBASE, LPTPM4_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 45 | {PCC2_RBASE, LPTPM5_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 46 | {PCC2_RBASE, LPIT1_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 47 | {PCC2_RBASE, LPSPI2_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 48 | {PCC2_RBASE, LPSPI3_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 49 | {PCC2_RBASE, LPI2C4_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 50 | {PCC2_RBASE, LPI2C5_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 51 | {PCC2_RBASE, LPUART4_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 52 | {PCC2_RBASE, LPUART5_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 53 | {PCC2_RBASE, FLEXIO1_PCC2_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 54 | {PCC2_RBASE, USBOTG0_PCC2_SLOT, CLKSRC_PER_PLAT, PCC_HAS_DIV}, |
| 55 | {PCC2_RBASE, USBOTG1_PCC2_SLOT, CLKSRC_PER_PLAT, PCC_HAS_DIV}, |
| 56 | {PCC2_RBASE, USBPHY_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 57 | {PCC2_RBASE, USB_PL301_PCC2_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 58 | {PCC2_RBASE, USDHC0_PCC2_SLOT, CLKSRC_PER_PLAT, PCC_HAS_DIV}, |
| 59 | {PCC2_RBASE, USDHC1_PCC2_SLOT, CLKSRC_PER_PLAT, PCC_HAS_DIV}, |
| 60 | {PCC2_RBASE, WDG1_PCC2_SLOT, CLKSRC_PER_BUS, PCC_HAS_DIV}, |
| 61 | {PCC2_RBASE, WDG2_PCC2_SLOT, CLKSRC_PER_BUS, PCC_HAS_DIV}, |
| 62 | |
| 63 | {PCC3_RBASE, LPTPM6_PCC3_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 64 | {PCC3_RBASE, LPTPM7_PCC3_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 65 | {PCC3_RBASE, LPI2C6_PCC3_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 66 | {PCC3_RBASE, LPI2C7_PCC3_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 67 | {PCC3_RBASE, LPUART6_PCC3_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 68 | {PCC3_RBASE, LPUART7_PCC3_SLOT, CLKSRC_PER_BUS, PCC_NO_DIV}, |
| 69 | {PCC3_RBASE, VIU0_PCC3_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 70 | {PCC3_RBASE, DSI0_PCC3_SLOT, CLKSRC_PER_BUS, PCC_HAS_DIV}, |
| 71 | {PCC3_RBASE, LCDIF0_PCC3_SLOT, CLKSRC_PER_PLAT, PCC_HAS_DIV}, |
| 72 | {PCC3_RBASE, MMDC0_PCC3_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 73 | {PCC3_RBASE, PORTC_PCC3_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 74 | {PCC3_RBASE, PORTD_PCC3_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 75 | {PCC3_RBASE, PORTE_PCC3_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 76 | {PCC3_RBASE, PORTF_PCC3_SLOT, CLKSRC_NO_PCS, PCC_NO_DIV}, |
| 77 | {PCC3_RBASE, GPU3D_PCC3_SLOT, CLKSRC_PER_PLAT, PCC_NO_DIV}, |
| 78 | {PCC3_RBASE, GPU2D_PCC3_SLOT, CLKSRC_PER_PLAT, PCC_NO_DIV}, |
| 79 | }; |
| 80 | |
| 81 | int pcc_clock_enable(enum pcc_clk clk, bool enable) |
| 82 | { |
| 83 | u32 reg, val; |
| 84 | |
| 85 | if (clk >= ARRAY_SIZE(pcc_arrays)) |
| 86 | return -EINVAL; |
| 87 | |
| 88 | reg = pcc_arrays[clk].pcc_base + pcc_arrays[clk].pcc_slot * 4; |
| 89 | |
| 90 | val = readl(reg); |
| 91 | |
| 92 | clk_debug("pcc_clock_enable: clk %d, reg 0x%x, val 0x%x, enable %d\n", |
| 93 | clk, reg, val, enable); |
| 94 | |
| 95 | if (!(val & PCC_PR_MASK) || (val & PCC_INUSE_MASK)) |
| 96 | return -EPERM; |
| 97 | |
| 98 | if (enable) |
| 99 | val |= PCC_CGC_MASK; |
| 100 | else |
| 101 | val &= ~PCC_CGC_MASK; |
| 102 | |
| 103 | writel(val, reg); |
| 104 | |
| 105 | clk_debug("pcc_clock_enable: val 0x%x\n", val); |
| 106 | |
| 107 | return 0; |
| 108 | } |
| 109 | |
| 110 | /* The clock source select needs clock is disabled */ |
| 111 | int pcc_clock_sel(enum pcc_clk clk, enum scg_clk src) |
| 112 | { |
| 113 | u32 reg, val, i, clksrc_type; |
| 114 | |
| 115 | if (clk >= ARRAY_SIZE(pcc_arrays)) |
| 116 | return -EINVAL; |
| 117 | |
| 118 | clksrc_type = pcc_arrays[clk].clksrc; |
| 119 | if (clksrc_type >= CLKSRC_NO_PCS) { |
| 120 | printf("No PCS field for the PCC %d, clksrc type %d\n", |
| 121 | clk, clksrc_type); |
| 122 | return -EPERM; |
| 123 | } |
| 124 | |
| 125 | for (i = 0; i < PCC_CLKSRC_NUM; i++) { |
| 126 | if (pcc_clksrc[clksrc_type][i] == src) { |
| 127 | /* Find the clock src, then set it to PCS */ |
| 128 | break; |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | if (i == PCC_CLKSRC_NUM) { |
| 133 | printf("Not find the parent scg_clk in PCS of PCC %d, invalid scg_clk %d\n", clk, src); |
| 134 | return -EINVAL; |
| 135 | } |
| 136 | |
| 137 | reg = pcc_arrays[clk].pcc_base + pcc_arrays[clk].pcc_slot * 4; |
| 138 | |
| 139 | val = readl(reg); |
| 140 | |
| 141 | clk_debug("pcc_clock_sel: clk %d, reg 0x%x, val 0x%x, clksrc_type %d\n", |
| 142 | clk, reg, val, clksrc_type); |
| 143 | |
| 144 | if (!(val & PCC_PR_MASK) || (val & PCC_INUSE_MASK) || |
| 145 | (val & PCC_CGC_MASK)) { |
| 146 | printf("Not permit to select clock source val = 0x%x\n", val); |
| 147 | return -EPERM; |
| 148 | } |
| 149 | |
| 150 | val &= ~PCC_PCS_MASK; |
| 151 | val |= ((i + 1) << PCC_PCS_OFFSET); |
| 152 | |
| 153 | writel(val, reg); |
| 154 | |
| 155 | clk_debug("pcc_clock_sel: val 0x%x\n", val); |
| 156 | |
| 157 | return 0; |
| 158 | } |
| 159 | |
| 160 | int pcc_clock_div_config(enum pcc_clk clk, bool frac, u8 div) |
| 161 | { |
| 162 | u32 reg, val; |
| 163 | |
| 164 | if (clk >= ARRAY_SIZE(pcc_arrays) || div > 8 || |
| 165 | (div == 1 && frac != 0)) |
| 166 | return -EINVAL; |
| 167 | |
| 168 | if (pcc_arrays[clk].div >= PCC_NO_DIV) { |
| 169 | printf("No DIV/FRAC field for the PCC %d\n", clk); |
| 170 | return -EPERM; |
| 171 | } |
| 172 | |
| 173 | reg = pcc_arrays[clk].pcc_base + pcc_arrays[clk].pcc_slot * 4; |
| 174 | |
| 175 | val = readl(reg); |
| 176 | |
| 177 | if (!(val & PCC_PR_MASK) || (val & PCC_INUSE_MASK) || |
| 178 | (val & PCC_CGC_MASK)) { |
| 179 | printf("Not permit to set div/frac val = 0x%x\n", val); |
| 180 | return -EPERM; |
| 181 | } |
| 182 | |
| 183 | if (frac) |
| 184 | val |= PCC_FRAC_MASK; |
| 185 | else |
| 186 | val &= ~PCC_FRAC_MASK; |
| 187 | |
| 188 | val &= ~PCC_PCD_MASK; |
| 189 | val |= (div - 1) & PCC_PCD_MASK; |
| 190 | |
| 191 | writel(val, reg); |
| 192 | |
| 193 | return 0; |
| 194 | } |
| 195 | |
| 196 | bool pcc_clock_is_enable(enum pcc_clk clk) |
| 197 | { |
| 198 | u32 reg, val; |
| 199 | |
| 200 | if (clk >= ARRAY_SIZE(pcc_arrays)) |
| 201 | return -EINVAL; |
| 202 | |
| 203 | reg = pcc_arrays[clk].pcc_base + pcc_arrays[clk].pcc_slot * 4; |
| 204 | val = readl(reg); |
| 205 | |
| 206 | if ((val & PCC_INUSE_MASK) || (val & PCC_CGC_MASK)) |
| 207 | return true; |
| 208 | |
| 209 | return false; |
| 210 | } |
| 211 | |
| 212 | int pcc_clock_get_clksrc(enum pcc_clk clk, enum scg_clk *src) |
| 213 | { |
| 214 | u32 reg, val, clksrc_type; |
| 215 | |
| 216 | if (clk >= ARRAY_SIZE(pcc_arrays)) |
| 217 | return -EINVAL; |
| 218 | |
| 219 | clksrc_type = pcc_arrays[clk].clksrc; |
| 220 | if (clksrc_type >= CLKSRC_NO_PCS) { |
| 221 | printf("No PCS field for the PCC %d, clksrc type %d\n", |
| 222 | clk, clksrc_type); |
| 223 | return -EPERM; |
| 224 | } |
| 225 | |
| 226 | reg = pcc_arrays[clk].pcc_base + pcc_arrays[clk].pcc_slot * 4; |
| 227 | |
| 228 | val = readl(reg); |
| 229 | |
| 230 | clk_debug("pcc_clock_get_clksrc: clk %d, reg 0x%x, val 0x%x, type %d\n", |
| 231 | clk, reg, val, clksrc_type); |
| 232 | |
| 233 | if (!(val & PCC_PR_MASK)) { |
| 234 | printf("This pcc slot is not present = 0x%x\n", val); |
| 235 | return -EPERM; |
| 236 | } |
| 237 | |
| 238 | val &= PCC_PCS_MASK; |
| 239 | val = (val >> PCC_PCS_OFFSET); |
| 240 | |
| 241 | if (!val) { |
| 242 | printf("Clock source is off\n"); |
| 243 | return -EIO; |
| 244 | } |
| 245 | |
| 246 | *src = pcc_clksrc[clksrc_type][val - 1]; |
| 247 | |
| 248 | clk_debug("pcc_clock_get_clksrc: parent scg clk %d\n", *src); |
| 249 | |
| 250 | return 0; |
| 251 | } |
| 252 | |
| 253 | u32 pcc_clock_get_rate(enum pcc_clk clk) |
| 254 | { |
| 255 | u32 reg, val, rate, frac, div; |
| 256 | enum scg_clk parent; |
| 257 | int ret; |
| 258 | |
| 259 | ret = pcc_clock_get_clksrc(clk, &parent); |
| 260 | if (ret) |
| 261 | return 0; |
| 262 | |
| 263 | rate = scg_clk_get_rate(parent); |
| 264 | |
| 265 | clk_debug("pcc_clock_get_rate: parent rate %u\n", rate); |
| 266 | |
| 267 | if (pcc_arrays[clk].div == PCC_HAS_DIV) { |
| 268 | reg = pcc_arrays[clk].pcc_base + pcc_arrays[clk].pcc_slot * 4; |
| 269 | val = readl(reg); |
| 270 | |
| 271 | frac = (val & PCC_FRAC_MASK) >> PCC_FRAC_OFFSET; |
| 272 | div = (val & PCC_PCD_MASK) >> PCC_PCD_OFFSET; |
| 273 | |
| 274 | /* |
| 275 | * Theoretically don't have overflow in the calc, |
| 276 | * the rate won't exceed 2G |
| 277 | */ |
| 278 | rate = rate * (frac + 1) / (div + 1); |
| 279 | } |
| 280 | |
| 281 | clk_debug("pcc_clock_get_rate: rate %u\n", rate); |
| 282 | return rate; |
| 283 | } |