015376f86adc48b77404a8a78a3b6dbdba8149cc
[platform/upstream/libdrm.git] / linux-core / i915_opregion.c
1 /*
2  *
3  * Copyright 2008 Intel Corporation <hong.liu@intel.com>
4  * Copyright 2008 Red Hat <mjg@redhat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sub license, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice (including the
15  * next paragraph) shall be included in all copies or substantial portions
16  * of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  * NON-INFRINGEMENT.  IN NO EVENT SHALL INTEL AND/OR ITS SUPPLIERS BE
22  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25  * SOFTWARE.
26  *
27  */
28
29 #include <linux/acpi.h>
30
31 #include "drmP.h"
32 #include "i915_drm.h"
33 #include "i915_drv.h"
34
35 #define PCI_ASLE 0xe4
36 #define PCI_ASLS 0xfc
37
38 #define OPREGION_SZ            (8*1024)
39 #define OPREGION_HEADER_OFFSET 0
40 #define OPREGION_ACPI_OFFSET   0x100
41 #define OPREGION_SWSCI_OFFSET  0x200
42 #define OPREGION_ASLE_OFFSET   0x300
43 #define OPREGION_VBT_OFFSET    0x1000
44
45 #define OPREGION_SIGNATURE "IntelGraphicsMem"
46 #define MBOX_ACPI      (1<<0)
47 #define MBOX_SWSCI     (1<<1)
48 #define MBOX_ASLE      (1<<2)
49
50 /* _DOD id definitions */
51 #define OUTPUT_CONNECTOR_MSK   0xf000
52 #define OUTPUT_CONNECTOR_OFFSET        12
53
54 #define OUTPUT_PORT_MSK                0x00f0
55 #define OUTPUT_PORT_OFFSET     4
56   #define OUTPUT_PORT_ANALOG   0
57   #define OUTPUT_PORT_LVDS     1
58   #define OUTPUT_PORT_SDVOB    2
59   #define OUTPUT_PORT_SDVOC    3
60   #define OUTPUT_PORT_TV       4
61
62 #define OUTPUT_DISPLAY_MSK     0x0f00
63 #define OUTPUT_DISPLAY_OFFSET  8
64   #define OUTPUT_DISPLAY_OTHER         0
65   #define OUTPUT_DISPLAY_VGA           1
66   #define OUTPUT_DISPLAY_TV            2
67   #define OUTPUT_DISPLAY_DIGI          3
68   #define OUTPUT_DISPLAY_FLAT_PANEL    4
69
70 /* predefined id for integrated LVDS and VGA connector */
71 #define OUTPUT_INT_LVDS        0x00000110
72 #define OUTPUT_INT_VGA 0x80000100
73
74 struct opregion_header {
75        u8 signature[16];
76        u32 size;
77        u32 opregion_ver;
78        u8 bios_ver[32];
79        u8 vbios_ver[16];
80        u8 driver_ver[16];
81        u32 mboxes;
82        u8 reserved[164];
83 } __attribute__((packed));
84
85 /* OpRegion mailbox #1: public ACPI methods */
86 struct opregion_acpi {
87        u32 drdy;       /* driver readiness */
88        u32 csts;       /* notification status */
89        u32 cevt;       /* current event */
90        u8 rsvd1[20];
91        u32 didl[8];    /* supported display devices ID list */
92        u32 cpdl[8];    /* currently presented display list */
93        u32 cadl[8];    /* currently active display list */
94        u32 nadl[8];    /* next active devices list */
95        u32 aslp;       /* ASL sleep time-out */
96        u32 tidx;       /* toggle table index */
97        u32 chpd;       /* current hotplug enable indicator */
98        u32 clid;       /* current lid state*/
99        u32 cdck;       /* current docking state */
100        u32 sxsw;       /* Sx state resume */
101        u32 evts;       /* ASL supported events */
102        u32 cnot;       /* current OS notification */
103        u32 nrdy;       /* driver status */
104        u8 rsvd2[60];
105 } __attribute__((packed));
106
107 /* OpRegion mailbox #2: SWSCI */
108 struct opregion_swsci {
109        u32 scic;       /* SWSCI command|status|data */
110        u32 parm;       /* command parameters */
111        u32 dslp;       /* driver sleep time-out */
112        u8 rsvd[244];
113 } __attribute__((packed));
114
115 /* OpRegion mailbox #3: ASLE */
116 struct opregion_asle {
117        u32 ardy;       /* driver readiness */
118        u32 aslc;       /* ASLE interrupt command */
119        u32 tche;       /* technology enabled indicator */
120        u32 alsi;       /* current ALS illuminance reading */
121        u32 bclp;       /* backlight brightness to set */
122        u32 pfit;       /* panel fitting state */
123        u32 cblv;       /* current brightness level */
124        u16 bclm[20];   /* backlight level duty cycle mapping table */
125        u32 cpfm;       /* current panel fitting mode */
126        u32 epfm;       /* enabled panel fitting modes */
127        u8 plut[74];    /* panel LUT and identifier */
128        u32 pfmb;       /* PWM freq and min brightness */
129        u8 rsvd[102];
130 } __attribute__((packed));
131
132 /* ASLE irq request bits */
133 #define ASLE_SET_ALS_ILLUM     (1 << 0)
134 #define ASLE_SET_BACKLIGHT     (1 << 1)
135 #define ASLE_SET_PFIT          (1 << 2)
136 #define ASLE_SET_PWM_FREQ      (1 << 3)
137 #define ASLE_REQ_MSK           0xf
138
139 /* response bits of ASLE irq request */
140 #define ASLE_ALS_ILLUM_FAIL    (2<<10)
141 #define ASLE_BACKLIGHT_FAIL    (2<<12)
142 #define ASLE_PFIT_FAIL         (2<<14)
143 #define ASLE_PWM_FREQ_FAIL     (2<<16)
144
145 /* ASLE backlight brightness to set */
146 #define ASLE_BCLP_VALID                (1<<31)
147 #define ASLE_BCLP_MSK          (~(1<<31))
148
149 /* ASLE panel fitting request */
150 #define ASLE_PFIT_VALID         (1<<31)
151 #define ASLE_PFIT_CENTER (1<<0)
152 #define ASLE_PFIT_STRETCH_TEXT (1<<1)
153 #define ASLE_PFIT_STRETCH_GFX (1<<2)
154
155 /* PWM frequency and minimum brightness */
156 #define ASLE_PFMB_BRIGHTNESS_MASK (0xff)
157 #define ASLE_PFMB_BRIGHTNESS_VALID (1<<8)
158 #define ASLE_PFMB_PWM_MASK (0x7ffffe00)
159 #define ASLE_PFMB_PWM_VALID (1<<31)
160
161 #define ASLE_CBLV_VALID         (1<<31)
162
163 static u32 asle_set_backlight(struct drm_device *dev, u32 bclp)
164 {
165         struct drm_i915_private *dev_priv = dev->dev_private;
166         struct opregion_asle *asle = dev_priv->opregion.asle;
167         u32 blc_pwm_ctl;
168         
169         if (!(bclp & ASLE_BCLP_VALID))
170                 return ASLE_BACKLIGHT_FAIL;
171         
172         bclp &= ASLE_BCLP_MSK;
173         if (bclp < 0 || bclp > 255)
174                 return ASLE_BACKLIGHT_FAIL;
175         
176         blc_pwm_ctl = I915_READ(BLC_PWM_CTL);
177         blc_pwm_ctl &= ~BACKLIGHT_DUTY_CYCLE_MASK;
178         I915_WRITE(BLC_PWM_CTL, blc_pwm_ctl | ((bclp * 0x101) -1));
179         asle->cblv = (bclp*0x64)/0xff | ASLE_CBLV_VALID;
180         
181         return 0;
182 }
183
184 static u32 asle_set_als_illum(struct drm_device *dev, u32 alsi)
185 {
186         return 0;
187 }
188
189 static u32 asle_set_pwm_freq(struct drm_device *dev, u32 pfmb)
190 {
191         struct drm_i915_private *dev_priv = dev->dev_private;
192         if (pfmb & ASLE_PFMB_PWM_VALID) {
193                 u32 blc_pwm_ctl = I915_READ(BLC_PWM_CTL);
194                 u32 pwm = pfmb & ASLE_PFMB_PWM_MASK;
195                 blc_pwm_ctl &= BACKLIGHT_DUTY_CYCLE_MASK;
196                 pwm = pwm >> 9;
197                 // FIXME - what do we do with the PWM?
198         }
199         return 0;
200 }
201
202 static u32 asle_set_pfit(struct drm_device *dev, u32 pfit)
203 {
204         if (!(pfit & ASLE_PFIT_VALID))
205                 return ASLE_PFIT_FAIL;
206         return 0;
207 }
208
209 void opregion_asle_intr(struct drm_device *dev)
210 {
211         struct drm_i915_private *dev_priv = dev->dev_private;
212         struct opregion_asle *asle = dev_priv->opregion.asle;
213         u32 asle_stat = 0;
214         u32 asle_req;
215
216         if (!asle)
217                 return;
218
219         asle_req = asle->aslc & ASLE_REQ_MSK;
220         
221         if (!asle_req) {
222                 DRM_DEBUG("non asle set request??\n");
223                 return;
224         }
225
226         if (asle_req & ASLE_SET_ALS_ILLUM)
227                 asle_stat |= asle_set_als_illum(dev, asle->alsi);
228         
229         if (asle_req & ASLE_SET_BACKLIGHT)
230                 asle_stat |= asle_set_backlight(dev, asle->bclp);
231         
232         if (asle_req & ASLE_SET_PFIT)
233                 asle_stat |= asle_set_pfit(dev, asle->pfit);
234         
235         if (asle_req & ASLE_SET_PWM_FREQ)
236                 asle_stat |= asle_set_pwm_freq(dev, asle->pfmb);
237         
238         asle->aslc = asle_stat;
239 }
240
241 #define ASLE_ALS_EN    (1<<0)
242 #define ASLE_BLC_EN    (1<<1)
243 #define ASLE_PFIT_EN   (1<<2)
244 #define ASLE_PFMB_EN   (1<<3)
245
246 void opregion_enable_asle(struct drm_device *dev)
247 {
248         struct drm_i915_private *dev_priv = dev->dev_private;
249         struct opregion_asle *asle = dev_priv->opregion.asle;
250
251         if (asle) {
252                 if (IS_MOBILE(dev)) {
253                         u32 pipeb_stats = I915_READ(PIPEBSTAT);
254                         /* Some hardware uses the legacy backlight controller
255                            to signal interrupts, so we need to set up pipe B
256                            to generate an IRQ on writes */
257                         pipeb_stats |= I915_LEGACY_BLC_EVENT_ENABLE;
258                         I915_WRITE(PIPEBSTAT, pipeb_stats);
259
260                         dev_priv->irq_mask_reg &=
261                                 ~I915_DISPLAY_PIPE_B_EVENT_INTERRUPT;
262                 }
263
264                 dev_priv->irq_mask_reg &= ~I915_ASLE_INTERRUPT;
265
266                 asle->tche = ASLE_ALS_EN | ASLE_BLC_EN | ASLE_PFIT_EN | 
267                         ASLE_PFMB_EN;
268                 asle->ardy = 1;
269         }
270 }
271
272 #define ACPI_EV_DISPLAY_SWITCH (1<<0)
273 #define ACPI_EV_LID            (1<<1)
274 #define ACPI_EV_DOCK           (1<<2)
275
276 static struct intel_opregion *system_opregion;
277
278 int intel_opregion_video_event(struct notifier_block *nb, unsigned long val,
279                                void *data)
280 {
281         /* The only video events relevant to opregion are 0x80. These indicate
282            either a docking event, lid switch or display switch request. In
283            Linux, these are handled by the dock, button and video drivers.
284            We might want to fix the video driver to be opregion-aware in
285            future, but right now we just indicate to the firmware that the
286            request has been handled */
287         
288         struct opregion_acpi *acpi;
289
290         if (!system_opregion)
291                 return NOTIFY_DONE;
292         
293         acpi = system_opregion->acpi;
294         acpi->csts = 0;
295
296         return NOTIFY_OK;
297 }
298
299 static struct notifier_block intel_opregion_notifier = {
300         .notifier_call = intel_opregion_video_event,
301 };
302
303 int intel_opregion_init(struct drm_device *dev)
304 {
305         struct drm_i915_private *dev_priv = dev->dev_private;
306         struct intel_opregion *opregion = &dev_priv->opregion;
307         void *base;
308         u32 asls, mboxes;
309         int err = 0;
310         
311         pci_read_config_dword(dev->pdev, PCI_ASLS, &asls);
312         DRM_DEBUG("graphic opregion physical addr: 0x%x\n", asls);
313         if (asls == 0) {
314                 DRM_DEBUG("ACPI OpRegion not supported!\n");
315                 return -ENOTSUPP;
316         }
317         
318         base = ioremap(asls, OPREGION_SZ);
319         if (!base)
320                 return -ENOMEM;
321         
322         opregion->header = base;
323         if (memcmp(opregion->header->signature, OPREGION_SIGNATURE, 16)) {
324                 DRM_DEBUG("opregion signature mismatch\n");
325                 err = -EINVAL;
326                 goto err_out;
327         }
328         
329         mboxes = opregion->header->mboxes;
330         if (mboxes & MBOX_ACPI) {
331                 DRM_DEBUG("Public ACPI methods supported\n");
332                 opregion->acpi = base + OPREGION_ACPI_OFFSET;
333         } else {
334                 DRM_DEBUG("Public ACPI methods not supported\n");
335                 err = -ENOTSUPP;
336                 goto err_out;
337         }
338         opregion->enabled = 1;
339         
340         if (mboxes & MBOX_SWSCI) {
341                 DRM_DEBUG("SWSCI supported\n");
342                 opregion->swsci = base + OPREGION_SWSCI_OFFSET;
343         }
344         if (mboxes & MBOX_ASLE) {
345                 DRM_DEBUG("ASLE supported\n");
346                 opregion->asle = base + OPREGION_ASLE_OFFSET;
347         }
348         
349         /* Notify BIOS we are ready to handle ACPI video ext notifs.
350          * Right now, all the events are handled by the ACPI video module.
351          * We don't actually need to do anything with them. */
352         opregion->acpi->csts = 0;
353         opregion->acpi->drdy = 1;
354
355         system_opregion = opregion;
356         register_acpi_notifier(&intel_opregion_notifier);
357         
358         return 0;
359         
360 err_out:
361         iounmap(opregion->header);
362         opregion->header = NULL;
363         return err;
364 }
365
366 void intel_opregion_free(struct drm_device *dev)
367 {
368         struct drm_i915_private *dev_priv = dev->dev_private;
369         struct intel_opregion *opregion = &dev_priv->opregion;
370         
371         if (!opregion->enabled)
372                 return;
373         
374         opregion->acpi->drdy = 0;
375         
376         system_opregion = NULL;
377         unregister_acpi_notifier(&intel_opregion_notifier);
378         
379         /* just clear all opregion memory pointers now */
380         iounmap(opregion->header);
381         opregion->header = NULL;
382         opregion->acpi = NULL;
383         opregion->swsci = NULL;
384         opregion->asle = NULL;
385         
386         opregion->enabled = 0;
387 }