/* * H8S2678 clock driver * * Copyright 2015 Yoshinori Sato <ysato@users.sourceforge.jp> */ #include <linux/clk-provider.h> #include <linux/err.h> #include <linux/device.h> #include <linux/of_address.h> #include <linux/slab.h> static DEFINE_SPINLOCK(clklock); #define MAX_FREQ 33333333 #define MIN_FREQ 8000000 struct pll_clock { struct clk_hw hw; void __iomem *sckcr; void __iomem *pllcr; }; #define to_pll_clock(_hw) container_of(_hw, struct pll_clock, hw) static unsigned long pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct pll_clock *pll_clock = to_pll_clock(hw); int mul = 1 << (readb(pll_clock->pllcr) & 3); return parent_rate * mul; } static long pll_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate) { int i, m = -1; long offset[3]; if (rate > MAX_FREQ) rate = MAX_FREQ; if (rate < MIN_FREQ) rate = MIN_FREQ; for (i = 0; i < 3; i++) offset[i] = abs(rate - (*prate * (1 << i))); for (i = 0; i < 3; i++) if (m < 0) m = i; else m = (offset[i] < offset[m])?i:m; return *prate * (1 << m); } static int pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { int pll; unsigned char val; unsigned long flags; struct pll_clock *pll_clock = to_pll_clock(hw); pll = ((rate / parent_rate) / 2) & 0x03; spin_lock_irqsave(&clklock, flags); val = readb(pll_clock->sckcr); val |= 0x08; writeb(val, pll_clock->sckcr); val = readb(pll_clock->pllcr); val &= ~0x03; val |= pll; writeb(val, pll_clock->pllcr); spin_unlock_irqrestore(&clklock, flags); return 0; } static const struct clk_ops pll_ops = { .recalc_rate = pll_recalc_rate, .round_rate = pll_round_rate, .set_rate = pll_set_rate, }; static void __init h8s2678_pll_clk_setup(struct device_node *node) { int num_parents; struct clk *clk; const char *clk_name = node->name; const char *parent_name; struct pll_clock *pll_clock; struct clk_init_data init; num_parents = of_clk_get_parent_count(node); if (num_parents < 1) { pr_err("%s: no parent found", clk_name); return; } pll_clock = kzalloc(sizeof(*pll_clock), GFP_KERNEL); if (!pll_clock) return; pll_clock->sckcr = of_iomap(node, 0); if (pll_clock->sckcr == NULL) { pr_err("%s: failed to map divide register", clk_name); goto free_clock; } pll_clock->pllcr = of_iomap(node, 1); if (pll_clock->pllcr == NULL) { pr_err("%s: failed to map multiply register", clk_name); goto unmap_sckcr; } parent_name = of_clk_get_parent_name(node, 0); init.name = clk_name; init.ops = &pll_ops; init.flags = CLK_IS_BASIC; init.parent_names = &parent_name; init.num_parents = 1; pll_clock->hw.init = &init; clk = clk_register(NULL, &pll_clock->hw); if (IS_ERR(clk)) { pr_err("%s: failed to register %s div clock (%ld)\n", __func__, clk_name, PTR_ERR(clk)); goto unmap_pllcr; } of_clk_add_provider(node, of_clk_src_simple_get, clk); return; unmap_pllcr: iounmap(pll_clock->pllcr); unmap_sckcr: iounmap(pll_clock->sckcr); free_clock: kfree(pll_clock); } CLK_OF_DECLARE(h8s2678_div_clk, "renesas,h8s2678-pll-clock", h8s2678_pll_clk_setup);