15dd0baa8Skettenis // SPDX-License-Identifier: GPL-2.0-only OR MIT
25dd0baa8Skettenis /* Copyright (C) The Asahi Linux Contributors */
35dd0baa8Skettenis
45dd0baa8Skettenis #include <drm/drm_atomic.h>
55dd0baa8Skettenis #include <drm/drm_crtc.h>
65dd0baa8Skettenis #include <drm/drm_drv.h>
75dd0baa8Skettenis #include <drm/drm_modeset_lock.h>
85dd0baa8Skettenis
95dd0baa8Skettenis #include <linux/backlight.h>
105dd0baa8Skettenis #include <linux/completion.h>
115dd0baa8Skettenis #include <linux/delay.h>
125dd0baa8Skettenis #include "linux/jiffies.h"
135dd0baa8Skettenis
145dd0baa8Skettenis #include "dcp.h"
155dd0baa8Skettenis #include "dcp-internal.h"
165dd0baa8Skettenis
175dd0baa8Skettenis #define MIN_BRIGHTNESS_PART1 2U
185dd0baa8Skettenis #define MAX_BRIGHTNESS_PART1 99U
195dd0baa8Skettenis #define MIN_BRIGHTNESS_PART2 103U
205dd0baa8Skettenis #define MAX_BRIGHTNESS_PART2 510U
215dd0baa8Skettenis
225dd0baa8Skettenis /*
235dd0baa8Skettenis * lookup for display brightness 2 to 99 nits
245dd0baa8Skettenis * */
255dd0baa8Skettenis static u32 brightness_part1[] = {
265dd0baa8Skettenis 0x0000000, 0x0810038, 0x0f000bd, 0x143011c,
275dd0baa8Skettenis 0x1850165, 0x1bc01a1, 0x1eb01d4, 0x2140200,
285dd0baa8Skettenis 0x2380227, 0x2590249, 0x2770269, 0x2930285,
295dd0baa8Skettenis 0x2ac02a0, 0x2c402b8, 0x2d902cf, 0x2ee02e4,
305dd0baa8Skettenis 0x30102f8, 0x314030b, 0x325031c, 0x335032d,
315dd0baa8Skettenis 0x345033d, 0x354034d, 0x362035b, 0x3700369,
325dd0baa8Skettenis 0x37d0377, 0x38a0384, 0x3960390, 0x3a2039c,
335dd0baa8Skettenis 0x3ad03a7, 0x3b803b3, 0x3c303bd, 0x3cd03c8,
345dd0baa8Skettenis 0x3d703d2, 0x3e103dc, 0x3ea03e5, 0x3f303ef,
355dd0baa8Skettenis 0x3fc03f8, 0x4050400, 0x40d0409, 0x4150411,
365dd0baa8Skettenis 0x41d0419, 0x4250421, 0x42d0429, 0x4340431,
375dd0baa8Skettenis 0x43c0438, 0x443043f, 0x44a0446, 0x451044d,
385dd0baa8Skettenis 0x4570454, 0x45e045b, 0x4640461, 0x46b0468,
395dd0baa8Skettenis 0x471046e, 0x4770474, 0x47d047a, 0x4830480,
405dd0baa8Skettenis 0x4890486, 0x48e048b, 0x4940491, 0x4990497,
415dd0baa8Skettenis 0x49f049c, 0x4a404a1, 0x4a904a7, 0x4ae04ac,
425dd0baa8Skettenis 0x4b304b1, 0x4b804b6, 0x4bd04bb, 0x4c204c0,
435dd0baa8Skettenis 0x4c704c5, 0x4cc04c9, 0x4d004ce, 0x4d504d3,
445dd0baa8Skettenis 0x4d904d7, 0x4de04dc, 0x4e204e0, 0x4e704e4,
455dd0baa8Skettenis 0x4eb04e9, 0x4ef04ed, 0x4f304f1, 0x4f704f5,
465dd0baa8Skettenis 0x4fb04f9, 0x4ff04fd, 0x5030501, 0x5070505,
475dd0baa8Skettenis 0x50b0509, 0x50f050d, 0x5130511, 0x5160515,
485dd0baa8Skettenis 0x51a0518, 0x51e051c, 0x5210520, 0x5250523,
495dd0baa8Skettenis 0x5290527, 0x52c052a, 0x52f052e, 0x5330531,
505dd0baa8Skettenis 0x5360535, 0x53a0538, 0x53d053b, 0x540053f,
515dd0baa8Skettenis 0x5440542, 0x5470545, 0x54a0548, 0x54d054c,
525dd0baa8Skettenis 0x550054f, 0x5530552, 0x5560555, 0x5590558,
535dd0baa8Skettenis 0x55c055b, 0x55f055e, 0x5620561, 0x5650564,
545dd0baa8Skettenis 0x5680567, 0x56b056a, 0x56e056d, 0x571056f,
555dd0baa8Skettenis 0x5740572, 0x5760575, 0x5790578, 0x57c057b,
565dd0baa8Skettenis 0x57f057d, 0x5810580, 0x5840583, 0x5870585,
575dd0baa8Skettenis 0x5890588, 0x58c058b, 0x58f058d
585dd0baa8Skettenis };
595dd0baa8Skettenis
605dd0baa8Skettenis static u32 brightness_part12[] = { 0x58f058d, 0x59d058f };
615dd0baa8Skettenis
625dd0baa8Skettenis /*
635dd0baa8Skettenis * lookup table for display brightness 103.3 to 510 nits
645dd0baa8Skettenis * */
655dd0baa8Skettenis static u32 brightness_part2[] = {
665dd0baa8Skettenis 0x59d058f, 0x5b805ab, 0x5d105c5, 0x5e805dd,
675dd0baa8Skettenis 0x5fe05f3, 0x6120608, 0x625061c, 0x637062e,
685dd0baa8Skettenis 0x6480640, 0x6580650, 0x6680660, 0x677066f,
695dd0baa8Skettenis 0x685067e, 0x693068c, 0x6a00699, 0x6ac06a6,
705dd0baa8Skettenis 0x6b806b2, 0x6c406be, 0x6cf06ca, 0x6da06d5,
715dd0baa8Skettenis 0x6e506df, 0x6ef06ea, 0x6f906f4, 0x70206fe,
725dd0baa8Skettenis 0x70c0707, 0x7150710, 0x71e0719, 0x7260722,
735dd0baa8Skettenis 0x72f072a, 0x7370733, 0x73f073b, 0x7470743,
745dd0baa8Skettenis 0x74e074a, 0x7560752, 0x75d0759, 0x7640760,
755dd0baa8Skettenis 0x76b0768, 0x772076e, 0x7780775, 0x77f077c,
765dd0baa8Skettenis 0x7850782, 0x78c0789, 0x792078f, 0x7980795,
775dd0baa8Skettenis 0x79e079b, 0x7a407a1, 0x7aa07a7, 0x7af07ac,
785dd0baa8Skettenis 0x7b507b2, 0x7ba07b8, 0x7c007bd, 0x7c507c2,
795dd0baa8Skettenis 0x7ca07c8, 0x7cf07cd, 0x7d407d2, 0x7d907d7,
805dd0baa8Skettenis 0x7de07dc, 0x7e307e1, 0x7e807e5, 0x7ec07ea,
815dd0baa8Skettenis 0x7f107ef, 0x7f607f3, 0x7fa07f8, 0x7fe07fc
825dd0baa8Skettenis };
835dd0baa8Skettenis
845dd0baa8Skettenis
dcp_get_brightness(struct backlight_device * bd)855dd0baa8Skettenis static int dcp_get_brightness(struct backlight_device *bd)
865dd0baa8Skettenis {
875dd0baa8Skettenis struct apple_dcp *dcp = bl_get_data(bd);
885dd0baa8Skettenis
895dd0baa8Skettenis return dcp->brightness.nits;
905dd0baa8Skettenis }
915dd0baa8Skettenis
925dd0baa8Skettenis #define SCALE_FACTOR (1 << 10)
935dd0baa8Skettenis
interpolate(int val,int min,int max,u32 * tbl,size_t tbl_size)945dd0baa8Skettenis static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size)
955dd0baa8Skettenis {
965dd0baa8Skettenis u32 frac;
975dd0baa8Skettenis u64 low, high;
985dd0baa8Skettenis u32 interpolated = (tbl_size - 1) * ((val - min) * SCALE_FACTOR) / (max - min);
995dd0baa8Skettenis
1005dd0baa8Skettenis size_t index = interpolated / SCALE_FACTOR;
1015dd0baa8Skettenis
102*44a5d259Skettenis if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u\n", index, val))
1035dd0baa8Skettenis return tbl[tbl_size / 2];
1045dd0baa8Skettenis
1055dd0baa8Skettenis frac = interpolated & (SCALE_FACTOR - 1);
1065dd0baa8Skettenis low = tbl[index];
1075dd0baa8Skettenis high = tbl[index + 1];
1085dd0baa8Skettenis
1095dd0baa8Skettenis return ((frac * high) + ((SCALE_FACTOR - frac) * low)) / SCALE_FACTOR;
1105dd0baa8Skettenis }
1115dd0baa8Skettenis
calculate_dac(struct apple_dcp * dcp,int val)1125dd0baa8Skettenis static u32 calculate_dac(struct apple_dcp *dcp, int val)
1135dd0baa8Skettenis {
1145dd0baa8Skettenis u32 dac;
1155dd0baa8Skettenis
1165dd0baa8Skettenis if (val <= MIN_BRIGHTNESS_PART1)
1175dd0baa8Skettenis return 16 * brightness_part1[0];
1185dd0baa8Skettenis else if (val == MAX_BRIGHTNESS_PART1)
1195dd0baa8Skettenis return 16 * brightness_part1[ARRAY_SIZE(brightness_part1) - 1];
1205dd0baa8Skettenis else if (val == MIN_BRIGHTNESS_PART2)
1215dd0baa8Skettenis return 16 * brightness_part2[0];
1225dd0baa8Skettenis else if (val >= MAX_BRIGHTNESS_PART2)
1235dd0baa8Skettenis return brightness_part2[ARRAY_SIZE(brightness_part2) - 1];
1245dd0baa8Skettenis
1255dd0baa8Skettenis if (val < MAX_BRIGHTNESS_PART1) {
1265dd0baa8Skettenis dac = interpolate(val, MIN_BRIGHTNESS_PART1, MAX_BRIGHTNESS_PART1,
1275dd0baa8Skettenis brightness_part1, ARRAY_SIZE(brightness_part1));
1285dd0baa8Skettenis } else if (val > MIN_BRIGHTNESS_PART2) {
1295dd0baa8Skettenis dac = interpolate(val, MIN_BRIGHTNESS_PART2, MAX_BRIGHTNESS_PART2,
1305dd0baa8Skettenis brightness_part2, ARRAY_SIZE(brightness_part2));
1315dd0baa8Skettenis } else {
1325dd0baa8Skettenis dac = interpolate(val, MAX_BRIGHTNESS_PART1, MIN_BRIGHTNESS_PART2,
1335dd0baa8Skettenis brightness_part12, ARRAY_SIZE(brightness_part12));
1345dd0baa8Skettenis }
1355dd0baa8Skettenis
1365dd0baa8Skettenis return 16 * dac;
1375dd0baa8Skettenis }
1385dd0baa8Skettenis
drm_crtc_set_brightness(struct apple_dcp * dcp)1395dd0baa8Skettenis static int drm_crtc_set_brightness(struct apple_dcp *dcp)
1405dd0baa8Skettenis {
1415dd0baa8Skettenis struct drm_atomic_state *state;
1425dd0baa8Skettenis struct drm_crtc_state *crtc_state;
1435dd0baa8Skettenis struct drm_modeset_acquire_ctx ctx;
1445dd0baa8Skettenis struct drm_crtc *crtc = &dcp->crtc->base;
1455dd0baa8Skettenis int ret = 0;
1465dd0baa8Skettenis
1475dd0baa8Skettenis DRM_MODESET_LOCK_ALL_BEGIN(crtc->dev, ctx, 0, ret);
1485dd0baa8Skettenis
1495dd0baa8Skettenis if (!dcp->brightness.update)
1505dd0baa8Skettenis goto done;
1515dd0baa8Skettenis
1525dd0baa8Skettenis state = drm_atomic_state_alloc(crtc->dev);
1535dd0baa8Skettenis if (!state)
1545dd0baa8Skettenis return -ENOMEM;
1555dd0baa8Skettenis
1565dd0baa8Skettenis state->acquire_ctx = &ctx;
1575dd0baa8Skettenis crtc_state = drm_atomic_get_crtc_state(state, crtc);
1585dd0baa8Skettenis if (IS_ERR(crtc_state)) {
1595dd0baa8Skettenis ret = PTR_ERR(crtc_state);
1605dd0baa8Skettenis goto fail;
1615dd0baa8Skettenis }
1625dd0baa8Skettenis
1635dd0baa8Skettenis crtc_state->color_mgmt_changed |= true;
1645dd0baa8Skettenis
1655dd0baa8Skettenis ret = drm_atomic_commit(state);
1665dd0baa8Skettenis
1675dd0baa8Skettenis fail:
1685dd0baa8Skettenis drm_atomic_state_put(state);
1695dd0baa8Skettenis done:
1705dd0baa8Skettenis DRM_MODESET_LOCK_ALL_END(crtc->dev, ctx, ret);
1715dd0baa8Skettenis
1725dd0baa8Skettenis return ret;
1735dd0baa8Skettenis }
1745dd0baa8Skettenis
dcp_backlight_update(struct apple_dcp * dcp)17548fd60f3Skettenis int dcp_backlight_update(struct apple_dcp *dcp)
1765dd0baa8Skettenis {
1775dd0baa8Skettenis /*
1785dd0baa8Skettenis * Do not actively try to change brightness if no mode is set.
1795dd0baa8Skettenis * TODO: should this be reflected the in backlight's power property?
1805dd0baa8Skettenis * defer this hopefully until it becomes irrelevant due to proper
1815dd0baa8Skettenis * drm integrated backlight handling
1825dd0baa8Skettenis */
1835dd0baa8Skettenis if (!dcp->valid_mode)
1845dd0baa8Skettenis return 0;
1855dd0baa8Skettenis
1865dd0baa8Skettenis /* Wait 1 vblank cycle in the hope an atomic swap has already updated
1875dd0baa8Skettenis * the brightness */
1885dd0baa8Skettenis drm_msleep((1001 + 23) / 24); // 42ms for 23.976 fps
1895dd0baa8Skettenis
1905dd0baa8Skettenis return drm_crtc_set_brightness(dcp);
1915dd0baa8Skettenis }
1925dd0baa8Skettenis
dcp_set_brightness(struct backlight_device * bd)19348fd60f3Skettenis static int dcp_set_brightness(struct backlight_device *bd)
19448fd60f3Skettenis {
19548fd60f3Skettenis int ret = 0;
19648fd60f3Skettenis struct apple_dcp *dcp = bl_get_data(bd);
19748fd60f3Skettenis struct drm_modeset_acquire_ctx ctx;
19848fd60f3Skettenis int brightness = backlight_get_brightness(bd);
19948fd60f3Skettenis
20048fd60f3Skettenis DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
20148fd60f3Skettenis
20248fd60f3Skettenis dcp->brightness.dac = calculate_dac(dcp, brightness);
20348fd60f3Skettenis dcp->brightness.update = true;
20448fd60f3Skettenis
20548fd60f3Skettenis DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
20648fd60f3Skettenis
20748fd60f3Skettenis return dcp_backlight_update(dcp);
20848fd60f3Skettenis }
20948fd60f3Skettenis
2105dd0baa8Skettenis static const struct backlight_ops dcp_backlight_ops = {
2115dd0baa8Skettenis .options = BL_CORE_SUSPENDRESUME,
2125dd0baa8Skettenis .get_brightness = dcp_get_brightness,
2135dd0baa8Skettenis .update_status = dcp_set_brightness,
2145dd0baa8Skettenis };
2155dd0baa8Skettenis
dcp_backlight_register(struct apple_dcp * dcp)2165dd0baa8Skettenis int dcp_backlight_register(struct apple_dcp *dcp)
2175dd0baa8Skettenis {
2185dd0baa8Skettenis struct device *dev = dcp->dev;
2195dd0baa8Skettenis struct backlight_device *bl_dev;
2205dd0baa8Skettenis struct backlight_properties props = {
2215dd0baa8Skettenis .type = BACKLIGHT_PLATFORM,
2225dd0baa8Skettenis .brightness = dcp->brightness.nits,
2235dd0baa8Skettenis .scale = BACKLIGHT_SCALE_LINEAR,
2245dd0baa8Skettenis };
2255dd0baa8Skettenis props.max_brightness = min(dcp->brightness.maximum, MAX_BRIGHTNESS_PART2 - 1);
2265dd0baa8Skettenis
2275dd0baa8Skettenis bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp,
2285dd0baa8Skettenis &dcp_backlight_ops, &props);
2295dd0baa8Skettenis if (IS_ERR(bl_dev))
2305dd0baa8Skettenis return PTR_ERR(bl_dev);
2315dd0baa8Skettenis
2325dd0baa8Skettenis dcp->brightness.bl_dev = bl_dev;
2335dd0baa8Skettenis dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits);
2345dd0baa8Skettenis
2355dd0baa8Skettenis return 0;
2365dd0baa8Skettenis }
237