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