diff options
| author | Boris Brezillon <boris.brezillon@free-electrons.com> | 2015-03-28 21:45:33 -0400 |
|---|---|---|
| committer | Michael Turquette <mturquette@linaro.org> | 2015-04-13 00:08:51 -0400 |
| commit | 4591243102faa8de92da320edea47219901461e9 (patch) | |
| tree | 896a83de022e746750b31953b2fef4b78fffa537 /drivers/clk/at91 | |
| parent | 3a9e9cb65be84d6c64fbe9c69a73c15d59f29454 (diff) | |
clk: at91: usb: propagate rate modification to the parent clk
The at91sam9n12 and at91sam9x5 usb clocks do not propagate rate
modification requests to their parents.
This causes a bug when the PLLB is left uninitialized by the bootloader
(PLL multiplier set to 0, or in other words, PLL rate = 0 Hz).
Implement the determinate_rate method and propagate the change rate
request to the parent clk.
Cc: <stable@vger.kernel.org> # v3.14+
Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Reported-by: Bo Shen <voice.shen@atmel.com>
Tested-by: Bo Shen <voice.shen@atmel.com>
Signed-off-by: Michael Turquette <mturquette@linaro.org>
Diffstat (limited to 'drivers/clk/at91')
| -rw-r--r-- | drivers/clk/at91/clk-usb.c | 64 |
1 files changed, 49 insertions, 15 deletions
diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c index a23ac0c724f0..0b7c3e8840ba 100644 --- a/drivers/clk/at91/clk-usb.c +++ b/drivers/clk/at91/clk-usb.c | |||
| @@ -56,22 +56,55 @@ static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk_hw *hw, | |||
| 56 | return DIV_ROUND_CLOSEST(parent_rate, (usbdiv + 1)); | 56 | return DIV_ROUND_CLOSEST(parent_rate, (usbdiv + 1)); |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | static long at91sam9x5_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate, | 59 | static long at91sam9x5_clk_usb_determine_rate(struct clk_hw *hw, |
| 60 | unsigned long *parent_rate) | 60 | unsigned long rate, |
| 61 | unsigned long min_rate, | ||
| 62 | unsigned long max_rate, | ||
| 63 | unsigned long *best_parent_rate, | ||
| 64 | struct clk_hw **best_parent_hw) | ||
| 61 | { | 65 | { |
| 62 | unsigned long div; | 66 | struct clk *parent = NULL; |
| 67 | long best_rate = -EINVAL; | ||
| 68 | unsigned long tmp_rate; | ||
| 69 | int best_diff = -1; | ||
| 70 | int tmp_diff; | ||
| 71 | int i; | ||
| 63 | 72 | ||
| 64 | if (!rate) | 73 | for (i = 0; i < __clk_get_num_parents(hw->clk); i++) { |
| 65 | return -EINVAL; | 74 | int div; |
| 66 | 75 | ||
| 67 | if (rate >= *parent_rate) | 76 | parent = clk_get_parent_by_index(hw->clk, i); |
| 68 | return *parent_rate; | 77 | if (!parent) |
| 78 | continue; | ||
| 79 | |||
| 80 | for (div = 1; div < SAM9X5_USB_MAX_DIV + 2; div++) { | ||
| 81 | unsigned long tmp_parent_rate; | ||
| 82 | |||
| 83 | tmp_parent_rate = rate * div; | ||
| 84 | tmp_parent_rate = __clk_round_rate(parent, | ||
| 85 | tmp_parent_rate); | ||
| 86 | tmp_rate = DIV_ROUND_CLOSEST(tmp_parent_rate, div); | ||
| 87 | if (tmp_rate < rate) | ||
| 88 | tmp_diff = rate - tmp_rate; | ||
| 89 | else | ||
| 90 | tmp_diff = tmp_rate - rate; | ||
| 91 | |||
| 92 | if (best_diff < 0 || best_diff > tmp_diff) { | ||
| 93 | best_rate = tmp_rate; | ||
| 94 | best_diff = tmp_diff; | ||
| 95 | *best_parent_rate = tmp_parent_rate; | ||
| 96 | *best_parent_hw = __clk_get_hw(parent); | ||
| 97 | } | ||
| 98 | |||
| 99 | if (!best_diff || tmp_rate < rate) | ||
| 100 | break; | ||
| 101 | } | ||
| 69 | 102 | ||
| 70 | div = DIV_ROUND_CLOSEST(*parent_rate, rate); | 103 | if (!best_diff) |
| 71 | if (div > SAM9X5_USB_MAX_DIV + 1) | 104 | break; |
| 72 | div = SAM9X5_USB_MAX_DIV + 1; | 105 | } |
| 73 | 106 | ||
| 74 | return DIV_ROUND_CLOSEST(*parent_rate, div); | 107 | return best_rate; |
| 75 | } | 108 | } |
| 76 | 109 | ||
| 77 | static int at91sam9x5_clk_usb_set_parent(struct clk_hw *hw, u8 index) | 110 | static int at91sam9x5_clk_usb_set_parent(struct clk_hw *hw, u8 index) |
| @@ -121,7 +154,7 @@ static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate, | |||
| 121 | 154 | ||
| 122 | static const struct clk_ops at91sam9x5_usb_ops = { | 155 | static const struct clk_ops at91sam9x5_usb_ops = { |
| 123 | .recalc_rate = at91sam9x5_clk_usb_recalc_rate, | 156 | .recalc_rate = at91sam9x5_clk_usb_recalc_rate, |
| 124 | .round_rate = at91sam9x5_clk_usb_round_rate, | 157 | .determine_rate = at91sam9x5_clk_usb_determine_rate, |
| 125 | .get_parent = at91sam9x5_clk_usb_get_parent, | 158 | .get_parent = at91sam9x5_clk_usb_get_parent, |
| 126 | .set_parent = at91sam9x5_clk_usb_set_parent, | 159 | .set_parent = at91sam9x5_clk_usb_set_parent, |
| 127 | .set_rate = at91sam9x5_clk_usb_set_rate, | 160 | .set_rate = at91sam9x5_clk_usb_set_rate, |
| @@ -159,7 +192,7 @@ static const struct clk_ops at91sam9n12_usb_ops = { | |||
| 159 | .disable = at91sam9n12_clk_usb_disable, | 192 | .disable = at91sam9n12_clk_usb_disable, |
| 160 | .is_enabled = at91sam9n12_clk_usb_is_enabled, | 193 | .is_enabled = at91sam9n12_clk_usb_is_enabled, |
| 161 | .recalc_rate = at91sam9x5_clk_usb_recalc_rate, | 194 | .recalc_rate = at91sam9x5_clk_usb_recalc_rate, |
| 162 | .round_rate = at91sam9x5_clk_usb_round_rate, | 195 | .determine_rate = at91sam9x5_clk_usb_determine_rate, |
| 163 | .set_rate = at91sam9x5_clk_usb_set_rate, | 196 | .set_rate = at91sam9x5_clk_usb_set_rate, |
| 164 | }; | 197 | }; |
| 165 | 198 | ||
| @@ -179,7 +212,8 @@ at91sam9x5_clk_register_usb(struct at91_pmc *pmc, const char *name, | |||
| 179 | init.ops = &at91sam9x5_usb_ops; | 212 | init.ops = &at91sam9x5_usb_ops; |
| 180 | init.parent_names = parent_names; | 213 | init.parent_names = parent_names; |
| 181 | init.num_parents = num_parents; | 214 | init.num_parents = num_parents; |
| 182 | init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; | 215 | init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE | |
| 216 | CLK_SET_RATE_PARENT; | ||
| 183 | 217 | ||
| 184 | usb->hw.init = &init; | 218 | usb->hw.init = &init; |
| 185 | usb->pmc = pmc; | 219 | usb->pmc = pmc; |
| @@ -207,7 +241,7 @@ at91sam9n12_clk_register_usb(struct at91_pmc *pmc, const char *name, | |||
| 207 | init.ops = &at91sam9n12_usb_ops; | 241 | init.ops = &at91sam9n12_usb_ops; |
| 208 | init.parent_names = &parent_name; | 242 | init.parent_names = &parent_name; |
| 209 | init.num_parents = 1; | 243 | init.num_parents = 1; |
| 210 | init.flags = CLK_SET_RATE_GATE; | 244 | init.flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT; |
| 211 | 245 | ||
| 212 | usb->hw.init = &init; | 246 | usb->hw.init = &init; |
| 213 | usb->pmc = pmc; | 247 | usb->pmc = pmc; |
