#include "clock.h"
 
-/*
- * DPLL5_FREQ_FOR_USBHOST: USBHOST and USBTLL are the only clocks
- * that are sourced by DPLL5, and both of these require this clock
- * to be at 120 MHz for proper operation.
- */
-#define DPLL5_FREQ_FOR_USBHOST         120000000
-
 #define OMAP3430ES2_ST_DSS_IDLE_SHIFT                  1
 #define OMAP3430ES2_ST_HSOTGUSB_IDLE_SHIFT             5
 #define OMAP3430ES2_ST_SSI_IDLE_SHIFT                  8
        struct clk *dpll5_clk;
        struct clk *dpll5_m2_clk;
 
+       /*
+        * Errata sprz319f advisory 2.1 documents a USB host clock drift issue
+        * that can be worked around using specially crafted dpll5 settings
+        * with a dpll5_m2 divider set to 8. Set the dpll5 rate to 8x the USB
+        * host clock rate, its .set_rate handler() will detect that frequency
+        * and use the errata settings.
+        */
        dpll5_clk = clk_get(NULL, "dpll5_ck");
-       clk_set_rate(dpll5_clk, DPLL5_FREQ_FOR_USBHOST);
+       clk_set_rate(dpll5_clk, OMAP3_DPLL5_FREQ_FOR_USBHOST * 8);
        clk_prepare_enable(dpll5_clk);
 
-       /* Program dpll5_m2_clk divider for no division */
+       /* Program dpll5_m2_clk divider */
        dpll5_m2_clk = clk_get(NULL, "dpll5_m2_ck");
        clk_prepare_enable(dpll5_m2_clk);
-       clk_set_rate(dpll5_m2_clk, DPLL5_FREQ_FOR_USBHOST);
+       clk_set_rate(dpll5_m2_clk, OMAP3_DPLL5_FREQ_FOR_USBHOST);
 
        clk_disable_unprepare(dpll5_m2_clk);
        clk_disable_unprepare(dpll5_clk);
 
 unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw,
                                    unsigned long parent_rate);
 
+/*
+ * OMAP3_DPLL5_FREQ_FOR_USBHOST: USBHOST and USBTLL are the only clocks
+ * that are sourced by DPLL5, and both of these require this clock
+ * to be at 120 MHz for proper operation.
+ */
+#define OMAP3_DPLL5_FREQ_FOR_USBHOST   120000000
+
 unsigned long omap3_dpll_recalc(struct clk_hw *hw, unsigned long parent_rate);
 int omap3_dpll4_set_rate(struct clk_hw *clk, unsigned long rate,
                         unsigned long parent_rate);
 int omap3_dpll4_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
                                    unsigned long parent_rate, u8 index);
+int omap3_dpll5_set_rate(struct clk_hw *hw, unsigned long rate,
+                        unsigned long parent_rate);
 void omap3_clk_lock_dpll5(void);
 
 unsigned long omap4_dpll_regm4xen_recalc(struct clk_hw *hw,
 
        .round_rate     = &omap2_dpll_round_rate,
 };
 
+static const struct clk_ops omap3_dpll5_ck_ops = {
+       .enable         = &omap3_noncore_dpll_enable,
+       .disable        = &omap3_noncore_dpll_disable,
+       .get_parent     = &omap2_init_dpll_parent,
+       .recalc_rate    = &omap3_dpll_recalc,
+       .set_rate       = &omap3_dpll5_set_rate,
+       .set_parent     = &omap3_noncore_dpll_set_parent,
+       .set_rate_and_parent    = &omap3_noncore_dpll_set_rate_and_parent,
+       .determine_rate = &omap3_noncore_dpll_determine_rate,
+       .round_rate     = &omap2_dpll_round_rate,
+};
+
 static const struct clk_ops omap3_dpll_per_ck_ops = {
        .enable         = &omap3_noncore_dpll_enable,
        .disable        = &omap3_noncore_dpll_disable,
                .modes = (1 << DPLL_LOW_POWER_BYPASS) | (1 << DPLL_LOCKED),
        };
 
-       of_ti_dpll_setup(node, &omap3_dpll_ck_ops, &dd);
+       if ((of_machine_is_compatible("ti,omap3630") ||
+            of_machine_is_compatible("ti,omap36xx")) &&
+           !strcmp(node->name, "dpll5_ck"))
+               of_ti_dpll_setup(node, &omap3_dpll5_ck_ops, &dd);
+       else
+               of_ti_dpll_setup(node, &omap3_dpll_ck_ops, &dd);
 }
 CLK_OF_DECLARE(ti_omap3_dpll_clock, "ti,omap3-dpll-clock",
               of_ti_omap3_dpll_setup);
 
        return omap3_noncore_dpll_set_rate_and_parent(hw, rate, parent_rate,
                                                      index);
 }
+
+/* Apply DM3730 errata sprz319 advisory 2.1. */
+static bool omap3_dpll5_apply_errata(struct clk_hw *hw,
+                                    unsigned long parent_rate)
+{
+       struct omap3_dpll5_settings {
+               unsigned int rate, m, n;
+       };
+
+       static const struct omap3_dpll5_settings precomputed[] = {
+               /*
+                * From DM3730 errata advisory 2.1, table 35 and 36.
+                * The N value is increased by 1 compared to the tables as the
+                * errata lists register values while last_rounded_field is the
+                * real divider value.
+                */
+               { 12000000,  80,  0 + 1 },
+               { 13000000, 443,  5 + 1 },
+               { 19200000,  50,  0 + 1 },
+               { 26000000, 443, 11 + 1 },
+               { 38400000,  25,  0 + 1 }
+       };
+
+       const struct omap3_dpll5_settings *d;
+       struct clk_hw_omap *clk = to_clk_hw_omap(hw);
+       struct dpll_data *dd;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(precomputed); ++i) {
+               if (parent_rate == precomputed[i].rate)
+                       break;
+       }
+
+       if (i == ARRAY_SIZE(precomputed))
+               return false;
+
+       d = &precomputed[i];
+
+       /* Update the M, N and rounded rate values and program the DPLL. */
+       dd = clk->dpll_data;
+       dd->last_rounded_m = d->m;
+       dd->last_rounded_n = d->n;
+       dd->last_rounded_rate = div_u64((u64)parent_rate * d->m, d->n);
+       omap3_noncore_dpll_program(clk, 0);
+
+       return true;
+}
+
+/**
+ * omap3_dpll5_set_rate - set rate for omap3 dpll5
+ * @hw: clock to change
+ * @rate: target rate for clock
+ * @parent_rate: rate of the parent clock
+ *
+ * Set rate for the DPLL5 clock. Apply the sprz319 advisory 2.1 on OMAP36xx if
+ * the DPLL is used for USB host (detected through the requested rate).
+ */
+int omap3_dpll5_set_rate(struct clk_hw *hw, unsigned long rate,
+                        unsigned long parent_rate)
+{
+       if (rate == OMAP3_DPLL5_FREQ_FOR_USBHOST * 8) {
+               if (omap3_dpll5_apply_errata(hw, parent_rate))
+                       return 0;
+       }
+
+       return omap3_noncore_dpll_set_rate(hw, rate, parent_rate);
+}