]>
Commit | Line | Data |
---|---|---|
6ee73861 BS |
1 | /* |
2 | * Copyright (C) 2009 Red Hat <mjg@redhat.com> | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining | |
5 | * a copy of this software and associated documentation files (the | |
6 | * "Software"), to deal in the Software without restriction, including | |
7 | * without limitation the rights to use, copy, modify, merge, publish, | |
8 | * distribute, sublicense, and/or sell copies of the Software, and to | |
9 | * permit persons to whom the Software is furnished to do so, subject to | |
10 | * the following conditions: | |
11 | * | |
12 | * The above copyright notice and this permission notice (including the | |
13 | * next paragraph) shall be included in all copies or substantial | |
14 | * portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
19 | * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE | |
20 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
21 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
22 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
23 | * | |
24 | */ | |
25 | ||
26 | /* | |
27 | * Authors: | |
28 | * Matthew Garrett <mjg@redhat.com> | |
29 | * | |
30 | * Register locations derived from NVClock by Roderick Colenbrander | |
31 | */ | |
32 | ||
b53ac1ee | 33 | #include <linux/apple-gmux.h> |
6ee73861 | 34 | #include <linux/backlight.h> |
db1a0ae2 | 35 | #include <linux/idr.h> |
6ee73861 | 36 | |
4dc28134 | 37 | #include "nouveau_drv.h" |
6ee73861 | 38 | #include "nouveau_reg.h" |
10b461e4 | 39 | #include "nouveau_encoder.h" |
6d757753 | 40 | #include "nouveau_connector.h" |
6ee73861 | 41 | |
db1a0ae2 PM |
42 | static struct ida bl_ida; |
43 | #define BL_NAME_SIZE 15 // 12 for name + 2 for digits + 1 for '\0' | |
44 | ||
6d757753 LP |
45 | struct nouveau_backlight { |
46 | struct backlight_device *dev; | |
db1a0ae2 PM |
47 | int id; |
48 | }; | |
49 | ||
50 | static bool | |
6d757753 LP |
51 | nouveau_get_backlight_name(char backlight_name[BL_NAME_SIZE], |
52 | struct nouveau_backlight *bl) | |
db1a0ae2 PM |
53 | { |
54 | const int nb = ida_simple_get(&bl_ida, 0, 0, GFP_KERNEL); | |
55 | if (nb < 0 || nb >= 100) | |
56 | return false; | |
57 | if (nb > 0) | |
58 | snprintf(backlight_name, BL_NAME_SIZE, "nv_backlight%d", nb); | |
59 | else | |
60 | snprintf(backlight_name, BL_NAME_SIZE, "nv_backlight"); | |
6d757753 | 61 | bl->id = nb; |
db1a0ae2 PM |
62 | return true; |
63 | } | |
64 | ||
73076481 BS |
65 | static int |
66 | nv40_get_intensity(struct backlight_device *bd) | |
6ee73861 | 67 | { |
e15e4c13 LP |
68 | struct nouveau_encoder *nv_encoder = bl_get_data(bd); |
69 | struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev); | |
1167c6bc | 70 | struct nvif_object *device = &drm->client.device.object; |
db2bec18 | 71 | int val = (nvif_rd32(device, NV40_PMC_BACKLIGHT) & |
f76e174b | 72 | NV40_PMC_BACKLIGHT_MASK) >> 16; |
6ee73861 BS |
73 | |
74 | return val; | |
75 | } | |
76 | ||
73076481 BS |
77 | static int |
78 | nv40_set_intensity(struct backlight_device *bd) | |
6ee73861 | 79 | { |
e15e4c13 LP |
80 | struct nouveau_encoder *nv_encoder = bl_get_data(bd); |
81 | struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev); | |
1167c6bc | 82 | struct nvif_object *device = &drm->client.device.object; |
6ee73861 | 83 | int val = bd->props.brightness; |
db2bec18 | 84 | int reg = nvif_rd32(device, NV40_PMC_BACKLIGHT); |
6ee73861 | 85 | |
db2bec18 | 86 | nvif_wr32(device, NV40_PMC_BACKLIGHT, |
f76e174b | 87 | (val << 16) | (reg & ~NV40_PMC_BACKLIGHT_MASK)); |
6ee73861 BS |
88 | |
89 | return 0; | |
90 | } | |
91 | ||
acc2472e | 92 | static const struct backlight_ops nv40_bl_ops = { |
6ee73861 BS |
93 | .options = BL_CORE_SUSPENDRESUME, |
94 | .get_brightness = nv40_get_intensity, | |
95 | .update_status = nv40_set_intensity, | |
96 | }; | |
97 | ||
73076481 | 98 | static int |
e15e4c13 LP |
99 | nv40_backlight_init(struct nouveau_encoder *encoder, |
100 | struct backlight_properties *props, | |
101 | const struct backlight_ops **ops) | |
6ee73861 | 102 | { |
e15e4c13 | 103 | struct nouveau_drm *drm = nouveau_drm(encoder->base.base.dev); |
1167c6bc | 104 | struct nvif_object *device = &drm->client.device.object; |
6ee73861 | 105 | |
db2bec18 | 106 | if (!(nvif_rd32(device, NV40_PMC_BACKLIGHT) & NV40_PMC_BACKLIGHT_MASK)) |
e15e4c13 | 107 | return -ENODEV; |
6ee73861 | 108 | |
e15e4c13 LP |
109 | props->type = BACKLIGHT_RAW; |
110 | props->max_brightness = 31; | |
111 | *ops = &nv40_bl_ops; | |
6ee73861 BS |
112 | return 0; |
113 | } | |
114 | ||
73076481 BS |
115 | static int |
116 | nv50_get_intensity(struct backlight_device *bd) | |
117 | { | |
10b461e4 | 118 | struct nouveau_encoder *nv_encoder = bl_get_data(bd); |
77145f1c | 119 | struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev); |
1167c6bc | 120 | struct nvif_object *device = &drm->client.device.object; |
9e75dc61 | 121 | int or = ffs(nv_encoder->dcb->or) - 1; |
09461459 BS |
122 | u32 div = 1025; |
123 | u32 val; | |
73076481 | 124 | |
db2bec18 | 125 | val = nvif_rd32(device, NV50_PDISP_SOR_PWM_CTL(or)); |
5024c54b | 126 | val &= NV50_PDISP_SOR_PWM_CTL_VAL; |
09461459 | 127 | return ((val * 100) + (div / 2)) / div; |
73076481 BS |
128 | } |
129 | ||
130 | static int | |
131 | nv50_set_intensity(struct backlight_device *bd) | |
132 | { | |
10b461e4 | 133 | struct nouveau_encoder *nv_encoder = bl_get_data(bd); |
77145f1c | 134 | struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev); |
1167c6bc | 135 | struct nvif_object *device = &drm->client.device.object; |
9e75dc61 | 136 | int or = ffs(nv_encoder->dcb->or) - 1; |
09461459 BS |
137 | u32 div = 1025; |
138 | u32 val = (bd->props.brightness * div) / 100; | |
73076481 | 139 | |
db2bec18 | 140 | nvif_wr32(device, NV50_PDISP_SOR_PWM_CTL(or), |
f76e174b | 141 | NV50_PDISP_SOR_PWM_CTL_NEW | val); |
73076481 BS |
142 | return 0; |
143 | } | |
144 | ||
145 | static const struct backlight_ops nv50_bl_ops = { | |
146 | .options = BL_CORE_SUSPENDRESUME, | |
147 | .get_brightness = nv50_get_intensity, | |
148 | .update_status = nv50_set_intensity, | |
149 | }; | |
150 | ||
5024c54b BS |
151 | static int |
152 | nva3_get_intensity(struct backlight_device *bd) | |
153 | { | |
154 | struct nouveau_encoder *nv_encoder = bl_get_data(bd); | |
77145f1c | 155 | struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev); |
1167c6bc | 156 | struct nvif_object *device = &drm->client.device.object; |
9e75dc61 | 157 | int or = ffs(nv_encoder->dcb->or) - 1; |
5024c54b BS |
158 | u32 div, val; |
159 | ||
db2bec18 BS |
160 | div = nvif_rd32(device, NV50_PDISP_SOR_PWM_DIV(or)); |
161 | val = nvif_rd32(device, NV50_PDISP_SOR_PWM_CTL(or)); | |
5024c54b BS |
162 | val &= NVA3_PDISP_SOR_PWM_CTL_VAL; |
163 | if (div && div >= val) | |
164 | return ((val * 100) + (div / 2)) / div; | |
165 | ||
166 | return 100; | |
167 | } | |
168 | ||
169 | static int | |
170 | nva3_set_intensity(struct backlight_device *bd) | |
171 | { | |
172 | struct nouveau_encoder *nv_encoder = bl_get_data(bd); | |
77145f1c | 173 | struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev); |
1167c6bc | 174 | struct nvif_object *device = &drm->client.device.object; |
9e75dc61 | 175 | int or = ffs(nv_encoder->dcb->or) - 1; |
5024c54b BS |
176 | u32 div, val; |
177 | ||
db2bec18 | 178 | div = nvif_rd32(device, NV50_PDISP_SOR_PWM_DIV(or)); |
5024c54b BS |
179 | val = (bd->props.brightness * div) / 100; |
180 | if (div) { | |
f76e174b LP |
181 | nvif_wr32(device, NV50_PDISP_SOR_PWM_CTL(or), |
182 | val | | |
183 | NV50_PDISP_SOR_PWM_CTL_NEW | | |
184 | NVA3_PDISP_SOR_PWM_CTL_UNK); | |
5024c54b BS |
185 | return 0; |
186 | } | |
187 | ||
188 | return -EINVAL; | |
189 | } | |
190 | ||
191 | static const struct backlight_ops nva3_bl_ops = { | |
192 | .options = BL_CORE_SUSPENDRESUME, | |
193 | .get_brightness = nva3_get_intensity, | |
194 | .update_status = nva3_set_intensity, | |
195 | }; | |
196 | ||
73076481 | 197 | static int |
e15e4c13 LP |
198 | nv50_backlight_init(struct nouveau_encoder *nv_encoder, |
199 | struct backlight_properties *props, | |
200 | const struct backlight_ops **ops) | |
6ee73861 | 201 | { |
e15e4c13 | 202 | struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev); |
1167c6bc | 203 | struct nvif_object *device = &drm->client.device.object; |
10b461e4 | 204 | |
9e75dc61 | 205 | if (!nvif_rd32(device, NV50_PDISP_SOR_PWM_CTL(ffs(nv_encoder->dcb->or) - 1))) |
e15e4c13 | 206 | return -ENODEV; |
6d757753 | 207 | |
1167c6bc BS |
208 | if (drm->client.device.info.chipset <= 0xa0 || |
209 | drm->client.device.info.chipset == 0xaa || | |
210 | drm->client.device.info.chipset == 0xac) | |
e15e4c13 | 211 | *ops = &nv50_bl_ops; |
5024c54b | 212 | else |
e15e4c13 | 213 | *ops = &nva3_bl_ops; |
5024c54b | 214 | |
e15e4c13 LP |
215 | props->type = BACKLIGHT_RAW; |
216 | props->max_brightness = 100; | |
db1a0ae2 | 217 | |
6ee73861 BS |
218 | return 0; |
219 | } | |
220 | ||
73076481 | 221 | int |
6d757753 | 222 | nouveau_backlight_init(struct drm_connector *connector) |
6ee73861 | 223 | { |
6d757753 | 224 | struct nouveau_drm *drm = nouveau_drm(connector->dev); |
e15e4c13 LP |
225 | struct nouveau_backlight *bl; |
226 | struct nouveau_encoder *nv_encoder = NULL; | |
1167c6bc | 227 | struct nvif_device *device = &drm->client.device; |
e15e4c13 LP |
228 | char backlight_name[BL_NAME_SIZE]; |
229 | struct backlight_properties props = {0}; | |
230 | const struct backlight_ops *ops; | |
231 | int ret; | |
76f2e2bc | 232 | |
b53ac1ee | 233 | if (apple_gmux_present()) { |
6d757753 | 234 | NV_INFO_ONCE(drm, "Apple GMUX detected: not registering Nouveau backlight interface\n"); |
b53ac1ee PM |
235 | return 0; |
236 | } | |
237 | ||
e15e4c13 LP |
238 | if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) |
239 | nv_encoder = find_encoder(connector, DCB_OUTPUT_LVDS); | |
240 | else if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) | |
241 | nv_encoder = find_encoder(connector, DCB_OUTPUT_DP); | |
242 | else | |
243 | return 0; | |
244 | ||
245 | if (!nv_encoder) | |
6d757753 | 246 | return 0; |
10b461e4 | 247 | |
6d757753 LP |
248 | switch (device->info.family) { |
249 | case NV_DEVICE_INFO_V0_CURIE: | |
e15e4c13 LP |
250 | ret = nv40_backlight_init(nv_encoder, &props, &ops); |
251 | break; | |
6d757753 LP |
252 | case NV_DEVICE_INFO_V0_TESLA: |
253 | case NV_DEVICE_INFO_V0_FERMI: | |
254 | case NV_DEVICE_INFO_V0_KEPLER: | |
255 | case NV_DEVICE_INFO_V0_MAXWELL: | |
15f77c4a BS |
256 | case NV_DEVICE_INFO_V0_PASCAL: |
257 | case NV_DEVICE_INFO_V0_VOLTA: | |
258 | case NV_DEVICE_INFO_V0_TURING: | |
e15e4c13 LP |
259 | ret = nv50_backlight_init(nv_encoder, &props, &ops); |
260 | break; | |
6d757753 LP |
261 | default: |
262 | return 0; | |
263 | } | |
e15e4c13 LP |
264 | |
265 | if (ret == -ENODEV) | |
266 | return 0; | |
267 | else if (ret) | |
268 | return ret; | |
269 | ||
270 | bl = kzalloc(sizeof(*bl), GFP_KERNEL); | |
271 | if (!bl) | |
272 | return -ENOMEM; | |
273 | ||
274 | if (!nouveau_get_backlight_name(backlight_name, bl)) { | |
275 | NV_ERROR(drm, "Failed to retrieve a unique name for the backlight interface\n"); | |
276 | goto fail_alloc; | |
277 | } | |
278 | ||
279 | bl->dev = backlight_device_register(backlight_name, connector->kdev, | |
280 | nv_encoder, ops, &props); | |
281 | if (IS_ERR(bl->dev)) { | |
282 | if (bl->id >= 0) | |
283 | ida_simple_remove(&bl_ida, bl->id); | |
284 | ret = PTR_ERR(bl->dev); | |
285 | goto fail_alloc; | |
286 | } | |
287 | ||
288 | nouveau_connector(connector)->backlight = bl; | |
289 | bl->dev->props.brightness = bl->dev->ops->get_brightness(bl->dev); | |
290 | backlight_update_status(bl->dev); | |
291 | ||
292 | return 0; | |
293 | ||
294 | fail_alloc: | |
295 | kfree(bl); | |
296 | return ret; | |
6ee73861 BS |
297 | } |
298 | ||
73076481 | 299 | void |
a4e05f41 | 300 | nouveau_backlight_fini(struct drm_connector *connector) |
6ee73861 | 301 | { |
6d757753 LP |
302 | struct nouveau_connector *nv_conn = nouveau_connector(connector); |
303 | struct nouveau_backlight *bl = nv_conn->backlight; | |
db1a0ae2 | 304 | |
6d757753 LP |
305 | if (!bl) |
306 | return; | |
6ee73861 | 307 | |
6d757753 LP |
308 | if (bl->id >= 0) |
309 | ida_simple_remove(&bl_ida, bl->id); | |
310 | ||
311 | backlight_device_unregister(bl->dev); | |
312 | nv_conn->backlight = NULL; | |
313 | kfree(bl); | |
6ee73861 | 314 | } |
db1a0ae2 PM |
315 | |
316 | void | |
317 | nouveau_backlight_ctor(void) | |
318 | { | |
319 | ida_init(&bl_ida); | |
320 | } | |
321 | ||
322 | void | |
323 | nouveau_backlight_dtor(void) | |
324 | { | |
325 | ida_destroy(&bl_ida); | |
326 | } |