diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index c86a7186e7729..1fce8415d9d2e 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -225,6 +225,8 @@ struct intel_opregion { struct opregion_header __iomem *header; struct opregion_acpi __iomem *acpi; struct opregion_swsci __iomem *swsci; + u32 swsci_gbda_sub_functions; + u32 swsci_sbcb_sub_functions; struct opregion_asle __iomem *asle; void __iomem *vbt; u32 __iomem *lid_state; diff --git a/drivers/gpu/drm/i915/intel_opregion.c b/drivers/gpu/drm/i915/intel_opregion.c index cfb8fb68f09c8..c45fec5b55217 100644 --- a/drivers/gpu/drm/i915/intel_opregion.c +++ b/drivers/gpu/drm/i915/intel_opregion.c @@ -36,8 +36,11 @@ #include "i915_drv.h" #include "intel_drv.h" -#define PCI_ASLE 0xe4 -#define PCI_ASLS 0xfc +#define PCI_ASLE 0xe4 +#define PCI_ASLS 0xfc +#define PCI_SWSCI 0xe8 +#define PCI_SWSCI_SCISEL (1 << 15) +#define PCI_SWSCI_GSSCIE (1 << 0) #define OPREGION_HEADER_OFFSET 0 #define OPREGION_ACPI_OFFSET 0x100 @@ -151,6 +154,51 @@ struct opregion_asle { #define ASLE_CBLV_VALID (1<<31) +/* Software System Control Interrupt (SWSCI) */ +#define SWSCI_SCIC_INDICATOR (1 << 0) +#define SWSCI_SCIC_MAIN_FUNCTION_SHIFT 1 +#define SWSCI_SCIC_MAIN_FUNCTION_MASK (0xf << 1) +#define SWSCI_SCIC_SUB_FUNCTION_SHIFT 8 +#define SWSCI_SCIC_SUB_FUNCTION_MASK (0xff << 8) +#define SWSCI_SCIC_EXIT_PARAMETER_SHIFT 8 +#define SWSCI_SCIC_EXIT_PARAMETER_MASK (0xff << 8) +#define SWSCI_SCIC_EXIT_STATUS_SHIFT 5 +#define SWSCI_SCIC_EXIT_STATUS_MASK (7 << 5) +#define SWSCI_SCIC_EXIT_STATUS_SUCCESS 1 + +#define SWSCI_FUNCTION_CODE(main, sub) \ + ((main) << SWSCI_SCIC_MAIN_FUNCTION_SHIFT | \ + (sub) << SWSCI_SCIC_SUB_FUNCTION_SHIFT) + +/* SWSCI: Get BIOS Data (GBDA) */ +#define SWSCI_GBDA 4 +#define SWSCI_GBDA_SUPPORTED_CALLS SWSCI_FUNCTION_CODE(SWSCI_GBDA, 0) +#define SWSCI_GBDA_REQUESTED_CALLBACKS SWSCI_FUNCTION_CODE(SWSCI_GBDA, 1) +#define SWSCI_GBDA_BOOT_DISPLAY_PREF SWSCI_FUNCTION_CODE(SWSCI_GBDA, 4) +#define SWSCI_GBDA_PANEL_DETAILS SWSCI_FUNCTION_CODE(SWSCI_GBDA, 5) +#define SWSCI_GBDA_TV_STANDARD SWSCI_FUNCTION_CODE(SWSCI_GBDA, 6) +#define SWSCI_GBDA_INTERNAL_GRAPHICS SWSCI_FUNCTION_CODE(SWSCI_GBDA, 7) +#define SWSCI_GBDA_SPREAD_SPECTRUM SWSCI_FUNCTION_CODE(SWSCI_GBDA, 10) + +/* SWSCI: System BIOS Callbacks (SBCB) */ +#define SWSCI_SBCB 6 +#define SWSCI_SBCB_SUPPORTED_CALLBACKS SWSCI_FUNCTION_CODE(SWSCI_SBCB, 0) +#define SWSCI_SBCB_INIT_COMPLETION SWSCI_FUNCTION_CODE(SWSCI_SBCB, 1) +#define SWSCI_SBCB_PRE_HIRES_SET_MODE SWSCI_FUNCTION_CODE(SWSCI_SBCB, 3) +#define SWSCI_SBCB_POST_HIRES_SET_MODE SWSCI_FUNCTION_CODE(SWSCI_SBCB, 4) +#define SWSCI_SBCB_DISPLAY_SWITCH SWSCI_FUNCTION_CODE(SWSCI_SBCB, 5) +#define SWSCI_SBCB_SET_TV_FORMAT SWSCI_FUNCTION_CODE(SWSCI_SBCB, 6) +#define SWSCI_SBCB_ADAPTER_POWER_STATE SWSCI_FUNCTION_CODE(SWSCI_SBCB, 7) +#define SWSCI_SBCB_DISPLAY_POWER_STATE SWSCI_FUNCTION_CODE(SWSCI_SBCB, 8) +#define SWSCI_SBCB_SET_BOOT_DISPLAY SWSCI_FUNCTION_CODE(SWSCI_SBCB, 9) +#define SWSCI_SBCB_SET_PANEL_DETAILS SWSCI_FUNCTION_CODE(SWSCI_SBCB, 10) +#define SWSCI_SBCB_SET_INTERNAL_GFX SWSCI_FUNCTION_CODE(SWSCI_SBCB, 11) +#define SWSCI_SBCB_POST_HIRES_TO_DOS_FS SWSCI_FUNCTION_CODE(SWSCI_SBCB, 16) +#define SWSCI_SBCB_SUSPEND_RESUME SWSCI_FUNCTION_CODE(SWSCI_SBCB, 17) +#define SWSCI_SBCB_SET_SPREAD_SPECTRUM SWSCI_FUNCTION_CODE(SWSCI_SBCB, 18) +#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) @@ -158,6 +206,91 @@ struct opregion_asle { #define ACPI_LVDS_OUTPUT (4<<8) #ifdef CONFIG_ACPI +static int swsci(struct drm_device *dev, u32 function, u32 parm, u32 *parm_out) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct opregion_swsci __iomem *swsci = dev_priv->opregion.swsci; + u32 main_function, sub_function, scic; + u16 pci_swsci; + u32 dslp; + + if (!swsci) + return -ENODEV; + + main_function = (function & SWSCI_SCIC_MAIN_FUNCTION_MASK) >> + SWSCI_SCIC_MAIN_FUNCTION_SHIFT; + sub_function = (function & SWSCI_SCIC_SUB_FUNCTION_MASK) >> + SWSCI_SCIC_SUB_FUNCTION_SHIFT; + + /* Check if we can call the function. See swsci_setup for details. */ + if (main_function == SWSCI_SBCB) { + if ((dev_priv->opregion.swsci_sbcb_sub_functions & + (1 << sub_function)) == 0) + return -EINVAL; + } else if (main_function == SWSCI_GBDA) { + if ((dev_priv->opregion.swsci_gbda_sub_functions & + (1 << sub_function)) == 0) + return -EINVAL; + } + + /* Driver sleep timeout in ms. */ + dslp = ioread32(&swsci->dslp); + if (!dslp) { + dslp = 2; + } else if (dslp > 500) { + /* Hey bios, trust must be earned. */ + WARN_ONCE(1, "excessive driver sleep timeout (DSPL) %u\n", dslp); + dslp = 500; + } + + /* The spec tells us to do this, but we are the only user... */ + scic = ioread32(&swsci->scic); + if (scic & SWSCI_SCIC_INDICATOR) { + DRM_DEBUG_DRIVER("SWSCI request already in progress\n"); + return -EBUSY; + } + + scic = function | SWSCI_SCIC_INDICATOR; + + iowrite32(parm, &swsci->parm); + iowrite32(scic, &swsci->scic); + + /* Ensure SCI event is selected and event trigger is cleared. */ + pci_read_config_word(dev->pdev, PCI_SWSCI, &pci_swsci); + if (!(pci_swsci & PCI_SWSCI_SCISEL) || (pci_swsci & PCI_SWSCI_GSSCIE)) { + pci_swsci |= PCI_SWSCI_SCISEL; + pci_swsci &= ~PCI_SWSCI_GSSCIE; + pci_write_config_word(dev->pdev, PCI_SWSCI, pci_swsci); + } + + /* Use event trigger to tell bios to check the mail. */ + pci_swsci |= PCI_SWSCI_GSSCIE; + pci_write_config_word(dev->pdev, PCI_SWSCI, pci_swsci); + + /* Poll for the result. */ +#define C (((scic = ioread32(&swsci->scic)) & SWSCI_SCIC_INDICATOR) == 0) + if (wait_for(C, dslp)) { + DRM_DEBUG_DRIVER("SWSCI request timed out\n"); + return -ETIMEDOUT; + } + + scic = (scic & SWSCI_SCIC_EXIT_STATUS_MASK) >> + SWSCI_SCIC_EXIT_STATUS_SHIFT; + + /* Note: scic == 0 is an error! */ + if (scic != SWSCI_SCIC_EXIT_STATUS_SUCCESS) { + DRM_DEBUG_DRIVER("SWSCI request error %u\n", scic); + return -EIO; + } + + if (parm_out) + *parm_out = ioread32(&swsci->parm); + + return 0; + +#undef C +} + static u32 asle_set_backlight(struct drm_device *dev, u32 bclp) { struct drm_i915_private *dev_priv = dev->dev_private; @@ -447,7 +580,66 @@ void intel_opregion_fini(struct drm_device *dev) opregion->asle = NULL; opregion->vbt = NULL; } -#endif + +static void swsci_setup(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_opregion *opregion = &dev_priv->opregion; + bool requested_callbacks = false; + u32 tmp; + + /* Sub-function code 0 is okay, let's allow them. */ + opregion->swsci_gbda_sub_functions = 1; + opregion->swsci_sbcb_sub_functions = 1; + + /* We use GBDA to ask for supported GBDA calls. */ + if (swsci(dev, SWSCI_GBDA_SUPPORTED_CALLS, 0, &tmp) == 0) { + /* make the bits match the sub-function codes */ + tmp <<= 1; + opregion->swsci_gbda_sub_functions |= tmp; + } + + /* + * We also use GBDA to ask for _requested_ SBCB callbacks. The driver + * must not call interfaces that are not specifically requested by the + * bios. + */ + if (swsci(dev, SWSCI_GBDA_REQUESTED_CALLBACKS, 0, &tmp) == 0) { + /* here, the bits already match sub-function codes */ + opregion->swsci_sbcb_sub_functions |= tmp; + requested_callbacks = true; + } + + /* + * But we use SBCB to ask for _supported_ SBCB calls. This does not mean + * the callback is _requested_. But we still can't call interfaces that + * are not requested. + */ + if (swsci(dev, SWSCI_SBCB_SUPPORTED_CALLBACKS, 0, &tmp) == 0) { + /* make the bits match the sub-function codes */ + u32 low = tmp & 0x7ff; + u32 high = tmp & ~0xfff; /* bit 11 is reserved */ + tmp = (high << 4) | (low << 1) | 1; + + /* best guess what to do with supported wrt requested */ + if (requested_callbacks) { + u32 req = opregion->swsci_sbcb_sub_functions; + if ((req & tmp) != req) + DRM_DEBUG_DRIVER("SWSCI BIOS requested (%08x) SBCB callbacks that are not supported (%08x)\n", req, tmp); + /* XXX: for now, trust the requested callbacks */ + /* opregion->swsci_sbcb_sub_functions &= tmp; */ + } else { + opregion->swsci_sbcb_sub_functions |= tmp; + } + } + + DRM_DEBUG_DRIVER("SWSCI GBDA callbacks %08x, SBCB callbacks %08x\n", + opregion->swsci_gbda_sub_functions, + opregion->swsci_sbcb_sub_functions); +} +#else /* CONFIG_ACPI */ +static inline void swsci_setup(struct drm_device *dev) {} +#endif /* CONFIG_ACPI */ int intel_opregion_setup(struct drm_device *dev) { @@ -490,6 +682,7 @@ int intel_opregion_setup(struct drm_device *dev) if (mboxes & MBOX_SWSCI) { DRM_DEBUG_DRIVER("SWSCI supported\n"); opregion->swsci = base + OPREGION_SWSCI_OFFSET; + swsci_setup(dev); } if (mboxes & MBOX_ASLE) { DRM_DEBUG_DRIVER("ASLE supported\n");