123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- /*
- * Ingenic SoC CGU driver
- *
- * Copyright (c) 2013-2015 Imagination Technologies
- * Author: Paul Burton <paul.burton@imgtec.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
- #include <linux/bitops.h>
- #include <linux/clk.h>
- #include <linux/clk-provider.h>
- #include <linux/clkdev.h>
- #include <linux/delay.h>
- #include <linux/math64.h>
- #include <linux/of.h>
- #include <linux/of_address.h>
- #include <linux/slab.h>
- #include <linux/spinlock.h>
- #include "cgu.h"
- #define MHZ (1000 * 1000)
- /**
- * ingenic_cgu_gate_get() - get the value of clock gate register bit
- * @cgu: reference to the CGU whose registers should be read
- * @info: info struct describing the gate bit
- *
- * Retrieves the state of the clock gate bit described by info. The
- * caller must hold cgu->lock.
- *
- * Return: true if the gate bit is set, else false.
- */
- static inline bool
- ingenic_cgu_gate_get(struct ingenic_cgu *cgu,
- const struct ingenic_cgu_gate_info *info)
- {
- return readl(cgu->base + info->reg) & BIT(info->bit);
- }
- /**
- * ingenic_cgu_gate_set() - set the value of clock gate register bit
- * @cgu: reference to the CGU whose registers should be modified
- * @info: info struct describing the gate bit
- * @val: non-zero to gate a clock, otherwise zero
- *
- * Sets the given gate bit in order to gate or ungate a clock.
- *
- * The caller must hold cgu->lock.
- */
- static inline void
- ingenic_cgu_gate_set(struct ingenic_cgu *cgu,
- const struct ingenic_cgu_gate_info *info, bool val)
- {
- u32 clkgr = readl(cgu->base + info->reg);
- if (val)
- clkgr |= BIT(info->bit);
- else
- clkgr &= ~BIT(info->bit);
- writel(clkgr, cgu->base + info->reg);
- }
- /*
- * PLL operations
- */
- static unsigned long
- ingenic_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- const struct ingenic_cgu_pll_info *pll_info;
- unsigned m, n, od_enc, od;
- bool bypass, enable;
- unsigned long flags;
- u32 ctl;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- BUG_ON(clk_info->type != CGU_CLK_PLL);
- pll_info = &clk_info->pll;
- spin_lock_irqsave(&cgu->lock, flags);
- ctl = readl(cgu->base + pll_info->reg);
- spin_unlock_irqrestore(&cgu->lock, flags);
- m = (ctl >> pll_info->m_shift) & GENMASK(pll_info->m_bits - 1, 0);
- m += pll_info->m_offset;
- n = (ctl >> pll_info->n_shift) & GENMASK(pll_info->n_bits - 1, 0);
- n += pll_info->n_offset;
- od_enc = ctl >> pll_info->od_shift;
- od_enc &= GENMASK(pll_info->od_bits - 1, 0);
- bypass = !!(ctl & BIT(pll_info->bypass_bit));
- enable = !!(ctl & BIT(pll_info->enable_bit));
- if (bypass)
- return parent_rate;
- if (!enable)
- return 0;
- for (od = 0; od < pll_info->od_max; od++) {
- if (pll_info->od_encoding[od] == od_enc)
- break;
- }
- BUG_ON(od == pll_info->od_max);
- od++;
- return div_u64((u64)parent_rate * m, n * od);
- }
- static unsigned long
- ingenic_pll_calc(const struct ingenic_cgu_clk_info *clk_info,
- unsigned long rate, unsigned long parent_rate,
- unsigned *pm, unsigned *pn, unsigned *pod)
- {
- const struct ingenic_cgu_pll_info *pll_info;
- unsigned m, n, od;
- pll_info = &clk_info->pll;
- od = 1;
- /*
- * The frequency after the input divider must be between 10 and 50 MHz.
- * The highest divider yields the best resolution.
- */
- n = parent_rate / (10 * MHZ);
- n = min_t(unsigned, n, 1 << clk_info->pll.n_bits);
- n = max_t(unsigned, n, pll_info->n_offset);
- m = (rate / MHZ) * od * n / (parent_rate / MHZ);
- m = min_t(unsigned, m, 1 << clk_info->pll.m_bits);
- m = max_t(unsigned, m, pll_info->m_offset);
- if (pm)
- *pm = m;
- if (pn)
- *pn = n;
- if (pod)
- *pod = od;
- return div_u64((u64)parent_rate * m, n * od);
- }
- static long
- ingenic_pll_round_rate(struct clk_hw *hw, unsigned long req_rate,
- unsigned long *prate)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- BUG_ON(clk_info->type != CGU_CLK_PLL);
- return ingenic_pll_calc(clk_info, req_rate, *prate, NULL, NULL, NULL);
- }
- static int
- ingenic_pll_set_rate(struct clk_hw *hw, unsigned long req_rate,
- unsigned long parent_rate)
- {
- const unsigned timeout = 100;
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- const struct ingenic_cgu_pll_info *pll_info;
- unsigned long rate, flags;
- unsigned m, n, od, i;
- u32 ctl;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- BUG_ON(clk_info->type != CGU_CLK_PLL);
- pll_info = &clk_info->pll;
- rate = ingenic_pll_calc(clk_info, req_rate, parent_rate,
- &m, &n, &od);
- if (rate != req_rate)
- pr_info("ingenic-cgu: request '%s' rate %luHz, actual %luHz\n",
- clk_info->name, req_rate, rate);
- spin_lock_irqsave(&cgu->lock, flags);
- ctl = readl(cgu->base + pll_info->reg);
- ctl &= ~(GENMASK(pll_info->m_bits - 1, 0) << pll_info->m_shift);
- ctl |= (m - pll_info->m_offset) << pll_info->m_shift;
- ctl &= ~(GENMASK(pll_info->n_bits - 1, 0) << pll_info->n_shift);
- ctl |= (n - pll_info->n_offset) << pll_info->n_shift;
- ctl &= ~(GENMASK(pll_info->od_bits - 1, 0) << pll_info->od_shift);
- ctl |= pll_info->od_encoding[od - 1] << pll_info->od_shift;
- ctl &= ~BIT(pll_info->bypass_bit);
- ctl |= BIT(pll_info->enable_bit);
- writel(ctl, cgu->base + pll_info->reg);
- /* wait for the PLL to stabilise */
- for (i = 0; i < timeout; i++) {
- ctl = readl(cgu->base + pll_info->reg);
- if (ctl & BIT(pll_info->stable_bit))
- break;
- mdelay(1);
- }
- spin_unlock_irqrestore(&cgu->lock, flags);
- if (i == timeout)
- return -EBUSY;
- return 0;
- }
- static const struct clk_ops ingenic_pll_ops = {
- .recalc_rate = ingenic_pll_recalc_rate,
- .round_rate = ingenic_pll_round_rate,
- .set_rate = ingenic_pll_set_rate,
- };
- /*
- * Operations for all non-PLL clocks
- */
- static u8 ingenic_clk_get_parent(struct clk_hw *hw)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- u32 reg;
- u8 i, hw_idx, idx = 0;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_MUX) {
- reg = readl(cgu->base + clk_info->mux.reg);
- hw_idx = (reg >> clk_info->mux.shift) &
- GENMASK(clk_info->mux.bits - 1, 0);
- /*
- * Convert the hardware index to the parent index by skipping
- * over any -1's in the parents array.
- */
- for (i = 0; i < hw_idx; i++) {
- if (clk_info->parents[i] != -1)
- idx++;
- }
- }
- return idx;
- }
- static int ingenic_clk_set_parent(struct clk_hw *hw, u8 idx)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- unsigned long flags;
- u8 curr_idx, hw_idx, num_poss;
- u32 reg, mask;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_MUX) {
- /*
- * Convert the parent index to the hardware index by adding
- * 1 for any -1 in the parents array preceding the given
- * index. That is, we want the index of idx'th entry in
- * clk_info->parents which does not equal -1.
- */
- hw_idx = curr_idx = 0;
- num_poss = 1 << clk_info->mux.bits;
- for (; hw_idx < num_poss; hw_idx++) {
- if (clk_info->parents[hw_idx] == -1)
- continue;
- if (curr_idx == idx)
- break;
- curr_idx++;
- }
- /* idx should always be a valid parent */
- BUG_ON(curr_idx != idx);
- mask = GENMASK(clk_info->mux.bits - 1, 0);
- mask <<= clk_info->mux.shift;
- spin_lock_irqsave(&cgu->lock, flags);
- /* write the register */
- reg = readl(cgu->base + clk_info->mux.reg);
- reg &= ~mask;
- reg |= hw_idx << clk_info->mux.shift;
- writel(reg, cgu->base + clk_info->mux.reg);
- spin_unlock_irqrestore(&cgu->lock, flags);
- return 0;
- }
- return idx ? -EINVAL : 0;
- }
- static unsigned long
- ingenic_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- unsigned long rate = parent_rate;
- u32 div_reg, div;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_DIV) {
- div_reg = readl(cgu->base + clk_info->div.reg);
- div = (div_reg >> clk_info->div.shift) &
- GENMASK(clk_info->div.bits - 1, 0);
- div += 1;
- rate /= div;
- }
- return rate;
- }
- static unsigned
- ingenic_clk_calc_div(const struct ingenic_cgu_clk_info *clk_info,
- unsigned long parent_rate, unsigned long req_rate)
- {
- unsigned div;
- /* calculate the divide */
- div = DIV_ROUND_UP(parent_rate, req_rate);
- /* and impose hardware constraints */
- div = min_t(unsigned, div, 1 << clk_info->div.bits);
- div = max_t(unsigned, div, 1);
- return div;
- }
- static long
- ingenic_clk_round_rate(struct clk_hw *hw, unsigned long req_rate,
- unsigned long *parent_rate)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- unsigned int div = 1;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_DIV)
- div = ingenic_clk_calc_div(clk_info, *parent_rate, req_rate);
- else if (clk_info->type & CGU_CLK_FIXDIV)
- div = clk_info->fixdiv.div;
- return DIV_ROUND_UP(*parent_rate, div);
- }
- static int
- ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
- unsigned long parent_rate)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- const unsigned timeout = 100;
- unsigned long rate, flags;
- unsigned div, i;
- u32 reg, mask;
- int ret = 0;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_DIV) {
- div = ingenic_clk_calc_div(clk_info, parent_rate, req_rate);
- rate = DIV_ROUND_UP(parent_rate, div);
- if (rate != req_rate)
- return -EINVAL;
- spin_lock_irqsave(&cgu->lock, flags);
- reg = readl(cgu->base + clk_info->div.reg);
- /* update the divide */
- mask = GENMASK(clk_info->div.bits - 1, 0);
- reg &= ~(mask << clk_info->div.shift);
- reg |= (div - 1) << clk_info->div.shift;
- /* clear the stop bit */
- if (clk_info->div.stop_bit != -1)
- reg &= ~BIT(clk_info->div.stop_bit);
- /* set the change enable bit */
- if (clk_info->div.ce_bit != -1)
- reg |= BIT(clk_info->div.ce_bit);
- /* update the hardware */
- writel(reg, cgu->base + clk_info->div.reg);
- /* wait for the change to take effect */
- if (clk_info->div.busy_bit != -1) {
- for (i = 0; i < timeout; i++) {
- reg = readl(cgu->base + clk_info->div.reg);
- if (!(reg & BIT(clk_info->div.busy_bit)))
- break;
- mdelay(1);
- }
- if (i == timeout)
- ret = -EBUSY;
- }
- spin_unlock_irqrestore(&cgu->lock, flags);
- return ret;
- }
- return -EINVAL;
- }
- static int ingenic_clk_enable(struct clk_hw *hw)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- unsigned long flags;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_GATE) {
- /* ungate the clock */
- spin_lock_irqsave(&cgu->lock, flags);
- ingenic_cgu_gate_set(cgu, &clk_info->gate, false);
- spin_unlock_irqrestore(&cgu->lock, flags);
- }
- return 0;
- }
- static void ingenic_clk_disable(struct clk_hw *hw)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- unsigned long flags;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_GATE) {
- /* gate the clock */
- spin_lock_irqsave(&cgu->lock, flags);
- ingenic_cgu_gate_set(cgu, &clk_info->gate, true);
- spin_unlock_irqrestore(&cgu->lock, flags);
- }
- }
- static int ingenic_clk_is_enabled(struct clk_hw *hw)
- {
- struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
- struct ingenic_cgu *cgu = ingenic_clk->cgu;
- const struct ingenic_cgu_clk_info *clk_info;
- unsigned long flags;
- int enabled = 1;
- clk_info = &cgu->clock_info[ingenic_clk->idx];
- if (clk_info->type & CGU_CLK_GATE) {
- spin_lock_irqsave(&cgu->lock, flags);
- enabled = !ingenic_cgu_gate_get(cgu, &clk_info->gate);
- spin_unlock_irqrestore(&cgu->lock, flags);
- }
- return enabled;
- }
- static const struct clk_ops ingenic_clk_ops = {
- .get_parent = ingenic_clk_get_parent,
- .set_parent = ingenic_clk_set_parent,
- .recalc_rate = ingenic_clk_recalc_rate,
- .round_rate = ingenic_clk_round_rate,
- .set_rate = ingenic_clk_set_rate,
- .enable = ingenic_clk_enable,
- .disable = ingenic_clk_disable,
- .is_enabled = ingenic_clk_is_enabled,
- };
- /*
- * Setup functions.
- */
- static int ingenic_register_clock(struct ingenic_cgu *cgu, unsigned idx)
- {
- const struct ingenic_cgu_clk_info *clk_info = &cgu->clock_info[idx];
- struct clk_init_data clk_init;
- struct ingenic_clk *ingenic_clk = NULL;
- struct clk *clk, *parent;
- const char *parent_names[4];
- unsigned caps, i, num_possible;
- int err = -EINVAL;
- BUILD_BUG_ON(ARRAY_SIZE(clk_info->parents) > ARRAY_SIZE(parent_names));
- if (clk_info->type == CGU_CLK_EXT) {
- clk = of_clk_get_by_name(cgu->np, clk_info->name);
- if (IS_ERR(clk)) {
- pr_err("%s: no external clock '%s' provided\n",
- __func__, clk_info->name);
- err = -ENODEV;
- goto out;
- }
- err = clk_register_clkdev(clk, clk_info->name, NULL);
- if (err) {
- clk_put(clk);
- goto out;
- }
- cgu->clocks.clks[idx] = clk;
- return 0;
- }
- if (!clk_info->type) {
- pr_err("%s: no clock type specified for '%s'\n", __func__,
- clk_info->name);
- goto out;
- }
- ingenic_clk = kzalloc(sizeof(*ingenic_clk), GFP_KERNEL);
- if (!ingenic_clk) {
- err = -ENOMEM;
- goto out;
- }
- ingenic_clk->hw.init = &clk_init;
- ingenic_clk->cgu = cgu;
- ingenic_clk->idx = idx;
- clk_init.name = clk_info->name;
- clk_init.flags = 0;
- clk_init.parent_names = parent_names;
- caps = clk_info->type;
- if (caps & (CGU_CLK_MUX | CGU_CLK_CUSTOM)) {
- clk_init.num_parents = 0;
- if (caps & CGU_CLK_MUX)
- num_possible = 1 << clk_info->mux.bits;
- else
- num_possible = ARRAY_SIZE(clk_info->parents);
- for (i = 0; i < num_possible; i++) {
- if (clk_info->parents[i] == -1)
- continue;
- parent = cgu->clocks.clks[clk_info->parents[i]];
- parent_names[clk_init.num_parents] =
- __clk_get_name(parent);
- clk_init.num_parents++;
- }
- BUG_ON(!clk_init.num_parents);
- BUG_ON(clk_init.num_parents > ARRAY_SIZE(parent_names));
- } else {
- BUG_ON(clk_info->parents[0] == -1);
- clk_init.num_parents = 1;
- parent = cgu->clocks.clks[clk_info->parents[0]];
- parent_names[0] = __clk_get_name(parent);
- }
- if (caps & CGU_CLK_CUSTOM) {
- clk_init.ops = clk_info->custom.clk_ops;
- caps &= ~CGU_CLK_CUSTOM;
- if (caps) {
- pr_err("%s: custom clock may not be combined with type 0x%x\n",
- __func__, caps);
- goto out;
- }
- } else if (caps & CGU_CLK_PLL) {
- clk_init.ops = &ingenic_pll_ops;
- caps &= ~CGU_CLK_PLL;
- if (caps) {
- pr_err("%s: PLL may not be combined with type 0x%x\n",
- __func__, caps);
- goto out;
- }
- } else {
- clk_init.ops = &ingenic_clk_ops;
- }
- /* nothing to do for gates or fixed dividers */
- caps &= ~(CGU_CLK_GATE | CGU_CLK_FIXDIV);
- if (caps & CGU_CLK_MUX) {
- if (!(caps & CGU_CLK_MUX_GLITCHFREE))
- clk_init.flags |= CLK_SET_PARENT_GATE;
- caps &= ~(CGU_CLK_MUX | CGU_CLK_MUX_GLITCHFREE);
- }
- if (caps & CGU_CLK_DIV) {
- caps &= ~CGU_CLK_DIV;
- } else {
- /* pass rate changes to the parent clock */
- clk_init.flags |= CLK_SET_RATE_PARENT;
- }
- if (caps) {
- pr_err("%s: unknown clock type 0x%x\n", __func__, caps);
- goto out;
- }
- clk = clk_register(NULL, &ingenic_clk->hw);
- if (IS_ERR(clk)) {
- pr_err("%s: failed to register clock '%s'\n", __func__,
- clk_info->name);
- err = PTR_ERR(clk);
- goto out;
- }
- err = clk_register_clkdev(clk, clk_info->name, NULL);
- if (err)
- goto out;
- cgu->clocks.clks[idx] = clk;
- out:
- if (err)
- kfree(ingenic_clk);
- return err;
- }
- struct ingenic_cgu *
- ingenic_cgu_new(const struct ingenic_cgu_clk_info *clock_info,
- unsigned num_clocks, struct device_node *np)
- {
- struct ingenic_cgu *cgu;
- cgu = kzalloc(sizeof(*cgu), GFP_KERNEL);
- if (!cgu)
- goto err_out;
- cgu->base = of_iomap(np, 0);
- if (!cgu->base) {
- pr_err("%s: failed to map CGU registers\n", __func__);
- goto err_out_free;
- }
- cgu->np = np;
- cgu->clock_info = clock_info;
- cgu->clocks.clk_num = num_clocks;
- spin_lock_init(&cgu->lock);
- return cgu;
- err_out_free:
- kfree(cgu);
- err_out:
- return NULL;
- }
- int ingenic_cgu_register_clocks(struct ingenic_cgu *cgu)
- {
- unsigned i;
- int err;
- cgu->clocks.clks = kcalloc(cgu->clocks.clk_num, sizeof(struct clk *),
- GFP_KERNEL);
- if (!cgu->clocks.clks) {
- err = -ENOMEM;
- goto err_out;
- }
- for (i = 0; i < cgu->clocks.clk_num; i++) {
- err = ingenic_register_clock(cgu, i);
- if (err)
- goto err_out_unregister;
- }
- err = of_clk_add_provider(cgu->np, of_clk_src_onecell_get,
- &cgu->clocks);
- if (err)
- goto err_out_unregister;
- return 0;
- err_out_unregister:
- for (i = 0; i < cgu->clocks.clk_num; i++) {
- if (!cgu->clocks.clks[i])
- continue;
- if (cgu->clock_info[i].type & CGU_CLK_EXT)
- clk_put(cgu->clocks.clks[i]);
- else
- clk_unregister(cgu->clocks.clks[i]);
- }
- kfree(cgu->clocks.clks);
- err_out:
- return err;
- }
|