Merge tag 'nfs-for-3.10-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs
[cascardo/linux.git] / drivers / misc / cs5535-mfgpt.c
index 9858f36..effd8c6 100644 (file)
 
 static int mfgpt_reset_timers;
 module_param_named(mfgptfix, mfgpt_reset_timers, int, 0644);
-MODULE_PARM_DESC(mfgptfix, "Reset the MFGPT timers during init; "
-               "required by some broken BIOSes (ie, TinyBIOS < 0.99).");
+MODULE_PARM_DESC(mfgptfix, "Try to reset the MFGPT timers during init; "
+               "required by some broken BIOSes (ie, TinyBIOS < 0.99) or kexec "
+               "(1 = reset the MFGPT using an undocumented bit, "
+               "2 = perform a soft reset by unconfiguring all timers); "
+               "use what works best for you.");
 
 struct cs5535_mfgpt_timer {
        struct cs5535_mfgpt_chip *chip;
@@ -255,6 +258,28 @@ static void reset_all_timers(void)
        wrmsr(MSR_MFGPT_SETUP, val, dummy);
 }
 
+/*
+ * This is another sledgehammer to reset all MFGPT timers.
+ * Instead of using the undocumented bit method it clears
+ * IRQ, NMI and RESET settings.
+ */
+static void soft_reset(void)
+{
+       int i;
+       struct cs5535_mfgpt_timer t;
+
+       for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
+               t.nr = i;
+
+               cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_RESET, 0);
+               cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_RESET, 0);
+               cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_NMI, 0);
+               cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_NMI, 0);
+               cs5535_mfgpt_toggle_event(&t, MFGPT_CMP1, MFGPT_EVENT_IRQ, 0);
+               cs5535_mfgpt_toggle_event(&t, MFGPT_CMP2, MFGPT_EVENT_IRQ, 0);
+       }
+}
+
 /*
  * Check whether any MFGPTs are available for the kernel to use.  In most
  * cases, firmware that uses AMD's VSA code will claim all timers during
@@ -271,15 +296,17 @@ static int scan_timers(struct cs5535_mfgpt_chip *mfgpt)
        int i;
 
        /* bios workaround */
-       if (mfgpt_reset_timers)
+       if (mfgpt_reset_timers == 1)
                reset_all_timers();
+       else if (mfgpt_reset_timers == 2)
+               soft_reset();
 
        /* just to be safe, protect this section w/ lock */
        spin_lock_irqsave(&mfgpt->lock, flags);
        for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
                timer.nr = i;
                val = cs5535_mfgpt_read(&timer, MFGPT_REG_SETUP);
-               if (!(val & MFGPT_SETUP_SETUP)) {
+               if (!(val & MFGPT_SETUP_SETUP) || mfgpt_reset_timers == 2) {
                        __set_bit(i, mfgpt->avail);
                        timers++;
                }
@@ -294,6 +321,12 @@ static int cs5535_mfgpt_probe(struct platform_device *pdev)
        struct resource *res;
        int err = -EIO, t;
 
+       if (mfgpt_reset_timers < 0 || mfgpt_reset_timers > 2) {
+               dev_err(&pdev->dev, "Bad mfgpt_reset_timers value: %i\n",
+                       mfgpt_reset_timers);
+               goto done;
+       }
+
        /* There are two ways to get the MFGPT base address; one is by
         * fetching it from MSR_LBAR_MFGPT, the other is by reading the
         * PCI BAR info.  The latter method is easier (especially across