diff mbox

drm/i915/opregion: work around buggy firmware that provides 8+ output devices

Message ID 52FC8C01.1040002@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Aaron Lu Feb. 13, 2014, 9:10 a.m. UTC
On 02/12/2014 06:31 PM, Chris Wilson wrote:
> On Wed, Feb 12, 2014 at 11:05:40AM +0800, Aaron Lu wrote:
>> The ACPI table on ASUS UX302LA has more than 8 output devices under the
>> graphics controller device node. The problem is, the real active output
>> device, the LCD panel, is listed the last. The result is, the LCD's
>> device id doesn't get recorded in the active device list CADL array and
>> when the _DCS control method for the LCD device is executed, it returns
>> 0x1d, meaning it is not active. This affects the hotkey delivery ASL
>> code that will not deliver a notification if the output device is not
>> active on backlight hotkey press.
>>
>> I don't see a clean way to solve this problem since the operation region
>> spec doesn't allow more than 8 output devices so we have no way of
>> storing all these output devices. The fact that output devices that have
>> _BCM control method usually means they have a higher possibility of being
>> used than those who don't made me choose a simple way to work around
>> the buggy firmware by replacing the last entry in CADL array with the one
>> that has _BCM control method. There is no specific reason why the last
>> entry is picked instead of others.
> 
> Another possibility is that the connector list is in rough priority
> order so might be useful for sorting the CADL array.
> 
> Since the CADL should only be a list of currently active devices, we
> could just bite the bullet and repopulate it correctly after every
> setcrtc.

Thanks for the suggestion. As a first step, does the following un-tested
patch look OK?



Thanks,
Aaron

Comments

Chris Wilson Feb. 13, 2014, 10:08 a.m. UTC | #1
On Thu, Feb 13, 2014 at 05:10:25PM +0800, Aaron Lu wrote:
> On 02/12/2014 06:31 PM, Chris Wilson wrote:
> > On Wed, Feb 12, 2014 at 11:05:40AM +0800, Aaron Lu wrote:
> >> The ACPI table on ASUS UX302LA has more than 8 output devices under the
> >> graphics controller device node. The problem is, the real active output
> >> device, the LCD panel, is listed the last. The result is, the LCD's
> >> device id doesn't get recorded in the active device list CADL array and
> >> when the _DCS control method for the LCD device is executed, it returns
> >> 0x1d, meaning it is not active. This affects the hotkey delivery ASL
> >> code that will not deliver a notification if the output device is not
> >> active on backlight hotkey press.
> >>
> >> I don't see a clean way to solve this problem since the operation region
> >> spec doesn't allow more than 8 output devices so we have no way of
> >> storing all these output devices. The fact that output devices that have
> >> _BCM control method usually means they have a higher possibility of being
> >> used than those who don't made me choose a simple way to work around
> >> the buggy firmware by replacing the last entry in CADL array with the one
> >> that has _BCM control method. There is no specific reason why the last
> >> entry is picked instead of others.
> > 
> > Another possibility is that the connector list is in rough priority
> > order so might be useful for sorting the CADL array.
> > 
> > Since the CADL should only be a list of currently active devices, we
> > could just bite the bullet and repopulate it correctly after every
> > setcrtc.
> 
> Thanks for the suggestion. As a first step, does the following un-tested
> patch look OK?

Yes. Maybe worth putting together the similar routines for blind
setting the didl and the cadl, or at least for computing the value from
the connector. For instance, the didl logic disagrees with the value of
index - is that relevant? I have a suspicion that the CADL entry should
match the DIDL entry for the connector, but that is not actually
mentioned in the opregion spec afaict.
-Chris
Daniel Vetter Feb. 13, 2014, 12:03 p.m. UTC | #2
On Thu, Feb 13, 2014 at 11:08 AM, Chris Wilson <chris@chris-wilson.co.uk> wrote:
> On Thu, Feb 13, 2014 at 05:10:25PM +0800, Aaron Lu wrote:
>> On 02/12/2014 06:31 PM, Chris Wilson wrote:
>> > On Wed, Feb 12, 2014 at 11:05:40AM +0800, Aaron Lu wrote:
>> >> The ACPI table on ASUS UX302LA has more than 8 output devices under the
>> >> graphics controller device node. The problem is, the real active output
>> >> device, the LCD panel, is listed the last. The result is, the LCD's
>> >> device id doesn't get recorded in the active device list CADL array and
>> >> when the _DCS control method for the LCD device is executed, it returns
>> >> 0x1d, meaning it is not active. This affects the hotkey delivery ASL
>> >> code that will not deliver a notification if the output device is not
>> >> active on backlight hotkey press.
>> >>
>> >> I don't see a clean way to solve this problem since the operation region
>> >> spec doesn't allow more than 8 output devices so we have no way of
>> >> storing all these output devices. The fact that output devices that have
>> >> _BCM control method usually means they have a higher possibility of being
>> >> used than those who don't made me choose a simple way to work around
>> >> the buggy firmware by replacing the last entry in CADL array with the one
>> >> that has _BCM control method. There is no specific reason why the last
>> >> entry is picked instead of others.
>> >
>> > Another possibility is that the connector list is in rough priority
>> > order so might be useful for sorting the CADL array.
>> >
>> > Since the CADL should only be a list of currently active devices, we
>> > could just bite the bullet and repopulate it correctly after every
>> > setcrtc.
>>
>> Thanks for the suggestion. As a first step, does the following un-tested
>> patch look OK?
>
> Yes. Maybe worth putting together the similar routines for blind
> setting the didl and the cadl, or at least for computing the value from
> the connector. For instance, the didl logic disagrees with the value of
> index - is that relevant? I have a suspicion that the CADL entry should
> match the DIDL entry for the connector, but that is not actually
> mentioned in the opregion spec afaict.

I think a problem is that often we have more than one output for a
given type. So we need to somehow match them up to make sure we put
the right ones intot didl/cadl lists. The issue here is that our
connectors don't match up perfectly with the acpi output entries
(since we have separate dp/hdmi outputs). But I think it would be
worthwhile trying to match them up and store a link from struct
intel_connector to the right acpi node acpi node.

Then we could generate the didl/cadl lists by walking all connectors
(only looking at the enabled ones for cadl) and evaluating the _ADR
node of the linked apci node. As long as we ensure that we don't have
duplicated entries we should be fine.

This is a bit more work though ... And I have no idea really how
firmware uses these lists (besides for backlight purposes apparently).
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/intel_opregion.c b/drivers/gpu/drm/i915/intel_opregion.c
index acde2945eb8a..afdd7d84fb32 100644
--- a/drivers/gpu/drm/i915/intel_opregion.c
+++ b/drivers/gpu/drm/i915/intel_opregion.c
@@ -220,11 +220,11 @@  struct opregion_asle {
 #define SWSCI_SBCB_POST_VBE_PM		SWSCI_FUNCTION_CODE(SWSCI_SBCB, 19)
 #define SWSCI_SBCB_ENABLE_DISABLE_AUDIO	SWSCI_FUNCTION_CODE(SWSCI_SBCB, 21)
 
-#define ACPI_OTHER_OUTPUT (0<<8)
-#define ACPI_VGA_OUTPUT (1<<8)
-#define ACPI_TV_OUTPUT (2<<8)
-#define ACPI_DIGITAL_OUTPUT (3<<8)
-#define ACPI_LVDS_OUTPUT (4<<8)
+#define ACPI_OTHER_OUTPUT 0
+#define ACPI_VGA_OUTPUT 1
+#define ACPI_TV_OUTPUT 2
+#define ACPI_DIGITAL_OUTPUT 3
+#define ACPI_LVDS_OUTPUT 4
 
 #define MAX_DSLP	1500
 
@@ -616,6 +616,7 @@  static void intel_didl_outputs(struct drm_device *dev)
 	acpi_status status;
 	u32 temp;
 	int i = 0;
+	int index[5] = {0};
 
 	handle = ACPI_HANDLE(&dev->pdev->dev);
 	if (!handle || acpi_bus_get_device(handle, &acpi_dev))
@@ -693,8 +694,8 @@  blind_set:
 			break;
 		}
 		temp = ioread32(&opregion->acpi->didl[i]);
-		iowrite32(temp | (1<<31) | output_type | i,
-			  &opregion->acpi->didl[i]);
+		iowrite32(temp | (1<<31) | (output_type << 8) |
+			  index[output_type]++, &opregion->acpi->didl[i]);
 		i++;
 	}
 	goto end;
@@ -705,18 +706,44 @@  static void intel_setup_cadls(struct drm_device *dev)
 	struct drm_i915_private *dev_priv = dev->dev_private;
 	struct intel_opregion *opregion = &dev_priv->opregion;
 	int i = 0;
+	int index[5] = {0};
 	u32 disp_id;
+	struct drm_connector *connector;
+
+	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+		int output_type = ACPI_OTHER_OUTPUT;
 
-	/* Initialize the CADL field by duplicating the DIDL values.
-	 * Technically, this is not always correct as display outputs may exist,
-	 * but not active. This initialization is necessary for some Clevo
-	 * laptops that check this field before processing the brightness and
-	 * display switching hotkeys. Just like DIDL, CADL is NULL-terminated if
-	 * there are less than eight devices. */
-	do {
-		disp_id = ioread32(&opregion->acpi->didl[i]);
+		switch (connector->connector_type) {
+		case DRM_MODE_CONNECTOR_VGA:
+		case DRM_MODE_CONNECTOR_DVIA:
+			output_type = ACPI_VGA_OUTPUT;
+			break;
+		case DRM_MODE_CONNECTOR_Composite:
+		case DRM_MODE_CONNECTOR_SVIDEO:
+		case DRM_MODE_CONNECTOR_Component:
+		case DRM_MODE_CONNECTOR_9PinDIN:
+			output_type = ACPI_TV_OUTPUT;
+			break;
+		case DRM_MODE_CONNECTOR_DVII:
+		case DRM_MODE_CONNECTOR_DVID:
+		case DRM_MODE_CONNECTOR_DisplayPort:
+		case DRM_MODE_CONNECTOR_HDMIA:
+		case DRM_MODE_CONNECTOR_HDMIB:
+			output_type = ACPI_DIGITAL_OUTPUT;
+			break;
+		case DRM_MODE_CONNECTOR_eDP:
+		case DRM_MODE_CONNECTOR_LVDS:
+			output_type = ACPI_LVDS_OUTPUT;
+			break;
+		}
+		disp_id = (opregion->acpi->didl[0] & (1 << 31)) |
+			  (output_type << 8) | index[output_type]++;
 		iowrite32(disp_id, &opregion->acpi->cadl[i]);
-	} while (++i < 8 && disp_id != 0);
+		i++;
+	}
+
+	if (i < 8)
+		iowrite32(0, &opregion->acpi->cadl[i]);
 }
 
 void intel_opregion_init(struct drm_device *dev)