diff options
Diffstat (limited to 'drivers/acpi/acpica/hwregs.c')
-rw-r--r-- | drivers/acpi/acpica/hwregs.c | 291 |
1 files changed, 259 insertions, 32 deletions
diff --git a/drivers/acpi/acpica/hwregs.c b/drivers/acpi/acpica/hwregs.c index 5ba0498412fd..0f18dbc9a37f 100644 --- a/drivers/acpi/acpica/hwregs.c +++ b/drivers/acpi/acpica/hwregs.c @@ -51,6 +51,10 @@ ACPI_MODULE_NAME("hwregs") #if (!ACPI_REDUCED_HARDWARE) /* Local Prototypes */ +static u8 +acpi_hw_get_access_bit_width(struct acpi_generic_address *reg, + u8 max_bit_width); + static acpi_status acpi_hw_read_multiple(u32 *value, struct acpi_generic_address *register_a, @@ -65,6 +69,48 @@ acpi_hw_write_multiple(u32 value, /****************************************************************************** * + * FUNCTION: acpi_hw_get_access_bit_width + * + * PARAMETERS: reg - GAS register structure + * max_bit_width - Max bit_width supported (32 or 64) + * + * RETURN: Status + * + * DESCRIPTION: Obtain optimal access bit width + * + ******************************************************************************/ + +static u8 +acpi_hw_get_access_bit_width(struct acpi_generic_address *reg, u8 max_bit_width) +{ + u64 address; + + if (!reg->access_width) { + /* + * Detect old register descriptors where only the bit_width field + * makes senses. The target address is copied to handle possible + * alignment issues. + */ + ACPI_MOVE_64_TO_64(&address, ®->address); + if (!reg->bit_offset && reg->bit_width && + ACPI_IS_POWER_OF_TWO(reg->bit_width) && + ACPI_IS_ALIGNED(reg->bit_width, 8) && + ACPI_IS_ALIGNED(address, reg->bit_width)) { + return (reg->bit_width); + } else { + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { + return (32); + } else { + return (max_bit_width); + } + } + } else { + return (1 << (reg->access_width + 2)); + } +} + +/****************************************************************************** + * * FUNCTION: acpi_hw_validate_register * * PARAMETERS: reg - GAS register structure @@ -83,6 +129,8 @@ acpi_status acpi_hw_validate_register(struct acpi_generic_address *reg, u8 max_bit_width, u64 *address) { + u8 bit_width; + u8 access_width; /* Must have a valid pointer to a GAS structure */ @@ -109,23 +157,25 @@ acpi_hw_validate_register(struct acpi_generic_address *reg, return (AE_SUPPORT); } - /* Validate the bit_width */ + /* Validate the access_width */ - if ((reg->bit_width != 8) && - (reg->bit_width != 16) && - (reg->bit_width != 32) && (reg->bit_width != max_bit_width)) { + if (reg->access_width > 4) { ACPI_ERROR((AE_INFO, - "Unsupported register bit width: 0x%X", - reg->bit_width)); + "Unsupported register access width: 0x%X", + reg->access_width)); return (AE_SUPPORT); } - /* Validate the bit_offset. Just a warning for now. */ + /* Validate the bit_width, convert access_width into number of bits */ - if (reg->bit_offset != 0) { + access_width = acpi_hw_get_access_bit_width(reg, max_bit_width); + bit_width = + ACPI_ROUND_UP(reg->bit_offset + reg->bit_width, access_width); + if (max_bit_width < bit_width) { ACPI_WARNING((AE_INFO, - "Unsupported register bit offset: 0x%X", - reg->bit_offset)); + "Requested bit width 0x%X is smaller than register bit width 0x%X", + max_bit_width, bit_width)); + return (AE_SUPPORT); } return (AE_OK); @@ -145,17 +195,19 @@ acpi_hw_validate_register(struct acpi_generic_address *reg, * 64-bit values is not needed. * * LIMITATIONS: <These limitations also apply to acpi_hw_write> - * bit_width must be exactly 8, 16, or 32. * space_ID must be system_memory or system_IO. - * bit_offset and access_width are currently ignored, as there has - * not been a need to implement these. * ******************************************************************************/ acpi_status acpi_hw_read(u32 *value, struct acpi_generic_address *reg) { u64 address; + u8 access_width; + u32 bit_width; + u8 bit_offset; u64 value64; + u32 value32; + u8 index; acpi_status status; ACPI_FUNCTION_NAME(hw_read); @@ -167,28 +219,75 @@ acpi_status acpi_hw_read(u32 *value, struct acpi_generic_address *reg) return (status); } - /* Initialize entire 32-bit return value to zero */ - + /* + * Initialize entire 32-bit return value to zero, convert access_width + * into number of bits based + */ *value = 0; + access_width = acpi_hw_get_access_bit_width(reg, 32); + bit_width = reg->bit_offset + reg->bit_width; + bit_offset = reg->bit_offset; /* * Two address spaces supported: Memory or IO. PCI_Config is * not supported here because the GAS structure is insufficient */ - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - status = acpi_os_read_memory((acpi_physical_address) - address, &value64, reg->bit_width); + index = 0; + while (bit_width) { + if (bit_offset >= access_width) { + value32 = 0; + bit_offset -= access_width; + } else { + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { + status = + acpi_os_read_memory((acpi_physical_address) + address + + index * + ACPI_DIV_8 + (access_width), + &value64, access_width); + value32 = (u32)value64; + } else { /* ACPI_ADR_SPACE_SYSTEM_IO, validated earlier */ + + status = acpi_hw_read_port((acpi_io_address) + address + + index * + ACPI_DIV_8 + (access_width), + &value32, + access_width); + } + + /* + * Use offset style bit masks because: + * bit_offset < access_width/bit_width < access_width, and + * access_width is ensured to be less than 32-bits by + * acpi_hw_validate_register(). + */ + if (bit_offset) { + value32 &= ACPI_MASK_BITS_BELOW(bit_offset); + bit_offset = 0; + } + if (bit_width < access_width) { + value32 &= ACPI_MASK_BITS_ABOVE(bit_width); + } + } - *value = (u32)value64; - } else { /* ACPI_ADR_SPACE_SYSTEM_IO, validated earlier */ + /* + * Use offset style bit writes because "Index * AccessWidth" is + * ensured to be less than 32-bits by acpi_hw_validate_register(). + */ + ACPI_SET_BITS(value, index * access_width, + ACPI_MASK_BITS_ABOVE_32(access_width), value32); - status = acpi_hw_read_port((acpi_io_address) - address, value, reg->bit_width); + bit_width -= + bit_width > access_width ? access_width : bit_width; + index++; } ACPI_DEBUG_PRINT((ACPI_DB_IO, "Read: %8.8X width %2d from %8.8X%8.8X (%s)\n", - *value, reg->bit_width, ACPI_FORMAT_UINT64(address), + *value, access_width, ACPI_FORMAT_UINT64(address), acpi_ut_get_region_name(reg->space_id))); return (status); @@ -212,6 +311,12 @@ acpi_status acpi_hw_read(u32 *value, struct acpi_generic_address *reg) acpi_status acpi_hw_write(u32 value, struct acpi_generic_address *reg) { u64 address; + u8 access_width; + u32 bit_width; + u8 bit_offset; + u64 value64; + u32 new_value32, old_value32; + u8 index; acpi_status status; ACPI_FUNCTION_NAME(hw_write); @@ -223,23 +328,145 @@ acpi_status acpi_hw_write(u32 value, struct acpi_generic_address *reg) return (status); } + /* Convert access_width into number of bits based */ + + access_width = acpi_hw_get_access_bit_width(reg, 32); + bit_width = reg->bit_offset + reg->bit_width; + bit_offset = reg->bit_offset; + /* * Two address spaces supported: Memory or IO. PCI_Config is * not supported here because the GAS structure is insufficient */ - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - status = acpi_os_write_memory((acpi_physical_address) - address, (u64)value, - reg->bit_width); - } else { /* ACPI_ADR_SPACE_SYSTEM_IO, validated earlier */ - - status = acpi_hw_write_port((acpi_io_address) - address, value, reg->bit_width); + index = 0; + while (bit_width) { + /* + * Use offset style bit reads because "Index * AccessWidth" is + * ensured to be less than 32-bits by acpi_hw_validate_register(). + */ + new_value32 = ACPI_GET_BITS(&value, index * access_width, + ACPI_MASK_BITS_ABOVE_32 + (access_width)); + + if (bit_offset >= access_width) { + bit_offset -= access_width; + } else { + /* + * Use offset style bit masks because access_width is ensured + * to be less than 32-bits by acpi_hw_validate_register() and + * bit_offset/bit_width is less than access_width here. + */ + if (bit_offset) { + new_value32 &= ACPI_MASK_BITS_BELOW(bit_offset); + } + if (bit_width < access_width) { + new_value32 &= ACPI_MASK_BITS_ABOVE(bit_width); + } + + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { + if (bit_offset || bit_width < access_width) { + /* + * Read old values in order not to modify the bits that + * are beyond the register bit_width/bit_offset setting. + */ + status = + acpi_os_read_memory((acpi_physical_address) + address + + index * + ACPI_DIV_8 + (access_width), + &value64, + access_width); + old_value32 = (u32)value64; + + /* + * Use offset style bit masks because access_width is + * ensured to be less than 32-bits by + * acpi_hw_validate_register() and bit_offset/bit_width is + * less than access_width here. + */ + if (bit_offset) { + old_value32 &= + ACPI_MASK_BITS_ABOVE + (bit_offset); + bit_offset = 0; + } + if (bit_width < access_width) { + old_value32 &= + ACPI_MASK_BITS_BELOW + (bit_width); + } + + new_value32 |= old_value32; + } + + value64 = (u64)new_value32; + status = + acpi_os_write_memory((acpi_physical_address) + address + + index * + ACPI_DIV_8 + (access_width), + value64, access_width); + } else { /* ACPI_ADR_SPACE_SYSTEM_IO, validated earlier */ + + if (bit_offset || bit_width < access_width) { + /* + * Read old values in order not to modify the bits that + * are beyond the register bit_width/bit_offset setting. + */ + status = + acpi_hw_read_port((acpi_io_address) + address + + index * + ACPI_DIV_8 + (access_width), + &old_value32, + access_width); + + /* + * Use offset style bit masks because access_width is + * ensured to be less than 32-bits by + * acpi_hw_validate_register() and bit_offset/bit_width is + * less than access_width here. + */ + if (bit_offset) { + old_value32 &= + ACPI_MASK_BITS_ABOVE + (bit_offset); + bit_offset = 0; + } + if (bit_width < access_width) { + old_value32 &= + ACPI_MASK_BITS_BELOW + (bit_width); + } + + new_value32 |= old_value32; + } + + status = acpi_hw_write_port((acpi_io_address) + address + + index * + ACPI_DIV_8 + (access_width), + new_value32, + access_width); + } + } + + /* + * Index * access_width is ensured to be less than 32-bits by + * acpi_hw_validate_register(). + */ + bit_width -= + bit_width > access_width ? access_width : bit_width; + index++; } ACPI_DEBUG_PRINT((ACPI_DB_IO, "Wrote: %8.8X width %2d to %8.8X%8.8X (%s)\n", - value, reg->bit_width, ACPI_FORMAT_UINT64(address), + value, access_width, ACPI_FORMAT_UINT64(address), acpi_ut_get_region_name(reg->space_id))); return (status); |