ACPICA: Clear PM register write-only bits on reading
[deliverable/linux.git] / drivers / acpi / acpica / hwregs.c
index 9c8162128c2b943b2d970c6f13e699d367e9cdf3..7b2fb602b5cbf59a441a98950078e34a6d7f862b 100644 (file)
@@ -129,6 +129,42 @@ struct acpi_bit_register_info *acpi_hw_get_bit_register_info(u32 register_id)
        return (&acpi_gbl_bit_register_info[register_id]);
 }
 
+/******************************************************************************
+ *
+ * FUNCTION:    acpi_hw_write_pm1_control
+ *
+ * PARAMETERS:  pm1a_control        - Value to be written to PM1A control
+ *              pm1b_control        - Value to be written to PM1B control
+ *
+ * RETURN:      Status
+ *
+ * DESCRIPTION: Write the PM1 A/B control registers. These registers are
+ *              different than than the PM1 A/B status and enable registers
+ *              in that different values can be written to the A/B registers.
+ *              Most notably, the SLP_TYP bits can be different, as per the
+ *              values returned from the _Sx predefined methods.
+ *
+ ******************************************************************************/
+
+acpi_status acpi_hw_write_pm1_control(u32 pm1a_control, u32 pm1b_control)
+{
+       acpi_status status;
+
+       ACPI_FUNCTION_TRACE(hw_write_pm1_control);
+
+       status = acpi_write(pm1a_control, &acpi_gbl_FADT.xpm1a_control_block);
+       if (ACPI_FAILURE(status)) {
+               return_ACPI_STATUS(status);
+       }
+
+       if (acpi_gbl_FADT.xpm1b_control_block.address) {
+               status =
+                   acpi_write(pm1b_control,
+                              &acpi_gbl_FADT.xpm1b_control_block);
+       }
+       return_ACPI_STATUS(status);
+}
+
 /******************************************************************************
  *
  * FUNCTION:    acpi_hw_register_read
@@ -171,6 +207,13 @@ acpi_hw_register_read(u32 register_id, u32 * return_value)
                                               xpm1a_control_block,
                                               &acpi_gbl_FADT.
                                               xpm1b_control_block);
+
+               /*
+                * Zero the write-only bits. From the ACPI specification, "Hardware
+                * Write-Only Bits": "Upon reads to registers with write-only bits,
+                * software masks out all write-only bits."
+                */
+               value &= ~ACPI_PM1_CONTROL_WRITEONLY_BITS;
                break;
 
        case ACPI_REGISTER_PM2_CONTROL: /* 8-bit access */
@@ -186,7 +229,7 @@ acpi_hw_register_read(u32 register_id, u32 * return_value)
        case ACPI_REGISTER_SMI_COMMAND_BLOCK:   /* 8-bit access */
 
                status =
-                   acpi_os_read_port(acpi_gbl_FADT.smi_command, &value, 8);
+                   acpi_hw_read_port(acpi_gbl_FADT.smi_command, &value, 8);
                break;
 
        default:
@@ -237,22 +280,17 @@ acpi_status acpi_hw_register_write(u32 register_id, u32 value)
 
        switch (register_id) {
        case ACPI_REGISTER_PM1_STATUS:  /* PM1 A/B: 16-bit access each */
-
-               /* Perform a read first to preserve certain bits (per ACPI spec) */
-
-               status = acpi_hw_read_multiple(&read_value,
-                                              &acpi_gbl_xpm1a_status,
-                                              &acpi_gbl_xpm1b_status);
-               if (ACPI_FAILURE(status)) {
-                       goto exit;
-               }
-
-               /* Insert the bits to be preserved */
-
-               ACPI_INSERT_BITS(value, ACPI_PM1_STATUS_PRESERVED_BITS,
-                                read_value);
-
-               /* Now we can write the data */
+               /*
+                * Handle the "ignored" bit in PM1 Status. According to the ACPI
+                * specification, ignored bits are to be preserved when writing.
+                * Normally, this would mean a read/modify/write sequence. However,
+                * preserving a bit in the status register is different. Writing a
+                * one clears the status, and writing a zero preserves the status.
+                * Therefore, we must always write zero to the ignored bit.
+                *
+                * This behavior is clarified in the ACPI 4.0 specification.
+                */
+               value &= ~ACPI_PM1_STATUS_PRESERVED_BITS;
 
                status = acpi_hw_write_multiple(value,
                                                &acpi_gbl_xpm1a_status,
@@ -295,17 +333,22 @@ acpi_status acpi_hw_register_write(u32 register_id, u32 value)
                                                xpm1b_control_block);
                break;
 
-       case ACPI_REGISTER_PM1A_CONTROL:        /* 16-bit access */
-
-               status = acpi_write(value, &acpi_gbl_FADT.xpm1a_control_block);
-               break;
+       case ACPI_REGISTER_PM2_CONTROL: /* 8-bit access */
 
-       case ACPI_REGISTER_PM1B_CONTROL:        /* 16-bit access */
+               /*
+                * For control registers, all reserved bits must be preserved,
+                * as per the ACPI spec.
+                */
+               status =
+                   acpi_read(&read_value, &acpi_gbl_FADT.xpm2_control_block);
+               if (ACPI_FAILURE(status)) {
+                       goto exit;
+               }
 
-               status = acpi_write(value, &acpi_gbl_FADT.xpm1b_control_block);
-               break;
+               /* Insert the bits to be preserved */
 
-       case ACPI_REGISTER_PM2_CONTROL: /* 8-bit access */
+               ACPI_INSERT_BITS(value, ACPI_PM2_CONTROL_PRESERVED_BITS,
+                                read_value);
 
                status = acpi_write(value, &acpi_gbl_FADT.xpm2_control_block);
                break;
@@ -320,7 +363,7 @@ acpi_status acpi_hw_register_write(u32 register_id, u32 value)
                /* SMI_CMD is currently always in IO space */
 
                status =
-                   acpi_os_write_port(acpi_gbl_FADT.smi_command, value, 8);
+                   acpi_hw_write_port(acpi_gbl_FADT.smi_command, value, 8);
                break;
 
        default:
@@ -372,9 +415,17 @@ acpi_hw_read_multiple(u32 *value,
                }
        }
 
-       /* Shift the B bits above the A bits */
-
-       *value = value_a | (value_b << register_a->bit_width);
+       /*
+        * OR the two return values together. No shifting or masking is necessary,
+        * because of how the PM1 registers are defined in the ACPI specification:
+        *
+        * "Although the bits can be split between the two register blocks (each
+        * register block has a unique pointer within the FADT), the bit positions
+        * are maintained. The register block with unimplemented bits (that is,
+        * those implemented in the other register block) always returns zeros,
+        * and writes have no side effects"
+        */
+       *value = (value_a | value_b);
        return (AE_OK);
 }
 
@@ -406,13 +457,20 @@ acpi_hw_write_multiple(u32 value,
                return (status);
        }
 
-       /* Second register is optional */
-
+       /*
+        * Second register is optional
+        *
+        * No bit shifting or clearing is necessary, because of how the PM1
+        * registers are defined in the ACPI specification:
+        *
+        * "Although the bits can be split between the two register blocks (each
+        * register block has a unique pointer within the FADT), the bit positions
+        * are maintained. The register block with unimplemented bits (that is,
+        * those implemented in the other register block) always returns zeros,
+        * and writes have no side effects"
+        */
        if (register_b->address) {
-
-               /* Normalize the B bits before write */
-
-               status = acpi_write(value >> register_a->bit_width, register_b);
+               status = acpi_write(value, register_b);
        }
 
        return (status);
This page took 0.028638 seconds and 5 git commands to generate.