| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * H8S2678 clock driver |
| * |
| * Copyright 2015 Yoshinori Sato <ysato@users.sourceforge.jp> |
| */ |
| |
| #include <linux/clk-provider.h> |
| #include <linux/device.h> |
| #include <linux/io.h> |
| #include <linux/err.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) |
| { |
| unsigned int num_parents; |
| const char *clk_name = node->name; |
| const char *parent_name; |
| struct pll_clock *pll_clock; |
| struct clk_init_data init; |
| int ret; |
| |
| num_parents = of_clk_get_parent_count(node); |
| if (!num_parents) { |
| pr_err("%s: no parent found\n", 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\n", 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\n", clk_name); |
| goto unmap_sckcr; |
| } |
| |
| parent_name = of_clk_get_parent_name(node, 0); |
| init.name = clk_name; |
| init.ops = &pll_ops; |
| init.flags = 0; |
| init.parent_names = &parent_name; |
| init.num_parents = 1; |
| pll_clock->hw.init = &init; |
| |
| ret = clk_hw_register(NULL, &pll_clock->hw); |
| if (ret) { |
| pr_err("%s: failed to register %s div clock (%d)\n", |
| __func__, clk_name, ret); |
| goto unmap_pllcr; |
| } |
| |
| of_clk_add_hw_provider(node, of_clk_hw_simple_get, &pll_clock->hw); |
| 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); |