[PATCH V3 3/6] clk: bcm2835: enable fractional and mash support
kernel at martin.sperl.org
kernel at martin.sperl.org
Thu Jan 14 05:45:38 PST 2016
From: Martin Sperl <kernel at martin.sperl.org>
The clk-bcm2835 driver right now does the correct calculation
of the fractional clock divider, but it does not set the FRAC
bit.
This patch enables FRAC for all clocks with frac_bits > 0
but allows to define the selection of a higher-order MASH
support instead of just FRAC.
Right now there are no limits imposed on maximum frequencies
when using MASH/FRAC is enabled.
We also implement all the documented limitations for
MASH=2/3 with regards to Divider upper/lower limits
(switching to a lower "MASH-level" if needed).
Signed-off-by: Martin Sperl <kernel at martin.sperl.org>
---
drivers/clk/bcm/clk-bcm2835.c | 122 +++++++++++++++++++++++++++++++++++++----
1 file changed, 110 insertions(+), 12 deletions(-)
Changelog:
V1->V2: added this patch
V2->V3: added checks on limits defined for mash
diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c
index 3d6490f..f5d483e 100644
--- a/drivers/clk/bcm/clk-bcm2835.c
+++ b/drivers/clk/bcm/clk-bcm2835.c
@@ -51,6 +51,7 @@
#define CM_GNRICCTL 0x000
#define CM_GNRICDIV 0x004
# define CM_DIV_FRAC_BITS 12
+# define CM_DIV_INT_BITS 12
#define CM_VPUCTL 0x008
#define CM_VPUDIV 0x00c
@@ -115,6 +116,10 @@
# define CM_GATE BIT(CM_GATE_BIT)
# define CM_BUSY BIT(7)
# define CM_BUSYD BIT(8)
+# define CM_MASH_BITS 2
+# define CM_MASH_SHIFT 9
+# define CM_MASH_MASK GENMASK(10, 9)
+# define CM_MASH(v) ((v << CM_MASH_SHIFT) & CM_MASH_MASK)
# define CM_SRC_SHIFT 0
# define CM_SRC_BITS 4
# define CM_SRC_MASK 0xf
@@ -281,6 +286,42 @@
#define LOCK_TIMEOUT_NS 100000000
#define BCM2835_MAX_FB_RATE 1750000000u
+enum bcm2835_clock_mash_type {
+ MASH_NONE = 0,
+ MASH_FRAC = 1,
+ MASH_2ND_ORDER = 2,
+ MASH_3RD_ORDER = 3
+};
+
+/* Helpers for Mash support */
+#define BCM2835_MASH_MAX_FREQ 25000000u
+#define DIVMASH_OFFSET (CM_DIV_INT_BITS + CM_DIV_FRAC_BITS)
+static inline u32 divmash_calc(enum bcm2835_clock_mash_type mash, u32 div)
+{
+ return div | (mash << DIVMASH_OFFSET);
+}
+
+static inline enum bcm2835_clock_mash_type divmash_get_mash(u32 divmash)
+{
+ return (divmash >> DIVMASH_OFFSET);
+}
+
+static inline u32 divmash_get_div(u32 divmash)
+{
+ return (divmash & GENMASK(DIVMASH_OFFSET - 1, 0));
+}
+
+static inline u32 divmash_get_divi(u32 divmash)
+{
+ return (divmash >> CM_DIV_FRAC_BITS) &
+ GENMASK(CM_DIV_INT_BITS - 1, 0);
+}
+
+static inline u32 divmash_get_divf(u32 divmash)
+{
+ return divmash & GENMASK(CM_DIV_FRAC_BITS - 1, 0);
+}
+
struct bcm2835_cprman {
struct device *dev;
void __iomem *regs;
@@ -632,6 +673,8 @@ struct bcm2835_clock_data {
u32 int_bits;
/* Number of fractional bits in the divider */
u32 frac_bits;
+ /* the mash value to use - see CM_MASH */
+ enum bcm2835_clock_mash_type mash;
bool is_vpu_clock;
};
@@ -1167,26 +1210,61 @@ static u32 bcm2835_clock_choose_div(struct clk_hw *hw,
GENMASK(CM_DIV_FRAC_BITS - data->frac_bits, 0) >> 1;
u64 temp = (u64)parent_rate << CM_DIV_FRAC_BITS;
u64 rem;
- u32 div;
+ u32 div, divi, divf;
+ const u32 divi_max = BIT(data->int_bits) - 1;
+ const u32 divi_min = 2;
rem = do_div(temp, rate);
div = temp;
+ divi = divmash_get_divi(div);
+ divf = divmash_get_divf(div);
/* Round up and mask off the unused bits */
if (round_up && ((div & unused_frac_mask) != 0 || rem != 0))
div += unused_frac_mask + 1;
div &= ~unused_frac_mask;
- /* Clamp to the limits. */
+ /*
+ * Check if we are within bounds for fractional/MASH dividers
+ * For offset values see Table 6-32 in BCM2835-ARM-Peripherials
+ * as well as the errata at:
+ * http://elinux.org/BCM2835_datasheet_errata#p105_table
+ */
+ if (divf) {
+ switch (data->mash) {
+ case MASH_3RD_ORDER:
+ if ((divi - 3 >= divi_min) &&
+ (divi + 4 <= divi_max) &&
+ (parent_rate / (divi - 3) <=
+ BCM2835_MASH_MAX_FREQ))
+ return divmash_calc(MASH_3RD_ORDER, div);
+ /* fall tru */
+ case MASH_2ND_ORDER:
+ if ((divi - 1 >= divi_min) &&
+ (divi + 2 <= divi_max) &&
+ (parent_rate / (divi - 1) <=
+ BCM2835_MASH_MAX_FREQ))
+ return divmash_calc(MASH_2ND_ORDER, div);
+ /* fall tru */
+ case MASH_FRAC:
+ if ((divi >= divi_min) &&
+ (divi + 1 <= divi_max))
+ return divmash_calc(MASH_FRAC, div);
+ /* fall tru to integer case */
+ case MASH_NONE:
+ break;
+ }
+ }
+ /* the non-frac case or frac out of bound case */
- /* divider must be >= 2 */
- div = max_t(u32, div, (2 << CM_DIV_FRAC_BITS));
+ /* must be <= max possible integer divider for this clock */
+ divi = min_t(u32, divi_max, divi);
- /* clamp to max divider allowed - max is integer divider */
- div = min_t(u32, div, GENMASK(data->int_bits + CM_DIV_FRAC_BITS - 1,
- CM_DIV_FRAC_BITS));
+ /* minimum divider is 2 */
+ divi = max_t(u32, 2, divi);
- return div;
+ /* return the divmash value based only on divi */
+ return divmash_calc(MASH_NONE, divi << CM_DIV_FRAC_BITS);
}
static long bcm2835_clock_rate_from_divisor(struct bcm2835_clock *clock,
@@ -1277,10 +1355,28 @@ static int bcm2835_clock_set_rate(struct clk_hw *hw,
struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
struct bcm2835_cprman *cprman = clock->cprman;
const struct bcm2835_clock_data *data = clock->data;
- u32 div = bcm2835_clock_choose_div(hw, rate, parent_rate, false);
+ u32 divmash = bcm2835_clock_choose_div(hw, rate, parent_rate, false);
+ enum bcm2835_clock_mash_type mash = divmash_get_mash(divmash);
+ u32 div = divmash_get_div(divmash);
+ u32 ctl;
+
+ spin_lock(&cprman->regs_lock);
+ /* check if divider is identical, then return */
+ if (div == cprman_read(cprman, data->div_reg))
+ goto unlock;
+ /* set the divider */
cprman_write(cprman, data->div_reg, div);
+ /* set mash to the selected value */
+ ctl = cprman_read(cprman, data->ctl_reg);
+ ctl &= ~CM_MASH_MASK;
+ ctl |= CM_MASH(mash) & CM_MASH_MASK;
+ cprman_write(cprman, data->ctl_reg, ctl);
+
+unlock:
+ spin_unlock(&cprman->regs_lock);
+
return 0;
}
@@ -1292,7 +1388,7 @@ static int bcm2835_clock_determine_rate(struct clk_hw *hw,
unsigned long rate, best_rate = 0;
unsigned long prate, best_prate = 0;
size_t i;
- u32 div;
+ u32 divmash, div;
/*
* Select parent clock that results in the closest but lower rate
@@ -1302,8 +1398,10 @@ static int bcm2835_clock_determine_rate(struct clk_hw *hw,
if (!parent)
continue;
prate = clk_hw_get_rate(parent);
- div = bcm2835_clock_choose_div(hw, req->rate, prate, true);
- rate = bcm2835_clock_rate_from_divisor(clock, prate, div);
+ divmash = bcm2835_clock_choose_div(hw, req->rate, prate,
+ true);
+ div = divmash_get_div(divmash);
+ rate = bcm2835_clock_rate_from_divisor(clock, prate, divmash);
if (rate > best_rate && rate <= req->rate) {
best_parent = parent;
best_prate = prate;
--
1.7.10.4
More information about the linux-arm-kernel
mailing list