|  | // 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); |