root/drivers/video/fbdev/aty/radeon_backlight.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Backlight code for ATI Radeon based graphic cards
 *
 * Copyright (c) 2000 Ani Joshi <ajoshi@kernel.crashing.org>
 * Copyright (c) 2003 Benjamin Herrenschmidt <benh@kernel.crashing.org>
 * Copyright (c) 2006 Michael Hanselmann <linux-kernel@hansmi.ch>
 */

#include "radeonfb.h"
#include <linux/backlight.h>
#include <linux/slab.h>

#ifdef CONFIG_PMAC_BACKLIGHT
#include <asm/backlight.h>
#endif

#define MAX_RADEON_LEVEL 0xFF

struct radeon_bl_privdata {
        struct radeonfb_info *rinfo;
        uint8_t negative;
};

static int radeon_bl_get_level_brightness(struct radeon_bl_privdata *pdata,
                int level)
{
        int rlevel;

        /* Get and convert the value */
        /* No locking of bl_curve since we read a single value */
        rlevel = pdata->rinfo->info->bl_curve[level] *
                 FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL;

        if (rlevel < 0)
                rlevel = 0;
        else if (rlevel > MAX_RADEON_LEVEL)
                rlevel = MAX_RADEON_LEVEL;

        if (pdata->negative)
                rlevel = MAX_RADEON_LEVEL - rlevel;

        return rlevel;
}

static int radeon_bl_update_status(struct backlight_device *bd)
{
        struct radeon_bl_privdata *pdata = bl_get_data(bd);
        struct radeonfb_info *rinfo = pdata->rinfo;
        u32 lvds_gen_cntl, tmpPixclksCntl;
        int level;

        if (rinfo->mon1_type != MT_LCD)
                return 0;

        /* We turn off the LCD completely instead of just dimming the
         * backlight. This provides some greater power saving and the display
         * is useless without backlight anyway.
         */
        level = backlight_get_brightness(bd);

        timer_delete_sync(&rinfo->lvds_timer);
        radeon_engine_idle();

        lvds_gen_cntl = INREG(LVDS_GEN_CNTL);
        if (level > 0) {
                lvds_gen_cntl &= ~LVDS_DISPLAY_DIS;
                if (!(lvds_gen_cntl & LVDS_BLON) || !(lvds_gen_cntl & LVDS_ON)) {
                        lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_DIGON);
                        lvds_gen_cntl |= LVDS_BLON | LVDS_EN;
                        OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
                        lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK;
                        lvds_gen_cntl |=
                                (radeon_bl_get_level_brightness(pdata, level) <<
                                 LVDS_BL_MOD_LEVEL_SHIFT);
                        lvds_gen_cntl |= LVDS_ON;
                        lvds_gen_cntl |= (rinfo->init_state.lvds_gen_cntl & LVDS_BL_MOD_EN);
                        rinfo->pending_lvds_gen_cntl = lvds_gen_cntl;
                        mod_timer(&rinfo->lvds_timer,
                                  jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay));
                } else {
                        lvds_gen_cntl &= ~LVDS_BL_MOD_LEVEL_MASK;
                        lvds_gen_cntl |=
                                (radeon_bl_get_level_brightness(pdata, level) <<
                                 LVDS_BL_MOD_LEVEL_SHIFT);
                        OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
                }
                rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK;
                rinfo->init_state.lvds_gen_cntl |= rinfo->pending_lvds_gen_cntl
                        & LVDS_STATE_MASK;
        } else {
                /* Asic bug, when turning off LVDS_ON, we have to make sure
                   RADEON_PIXCLK_LVDS_ALWAYS_ON bit is off
                */
                tmpPixclksCntl = INPLL(PIXCLKS_CNTL);
                if (rinfo->is_mobility || rinfo->is_IGP)
                        OUTPLLP(PIXCLKS_CNTL, 0, ~PIXCLK_LVDS_ALWAYS_ONb);
                lvds_gen_cntl &= ~(LVDS_BL_MOD_LEVEL_MASK | LVDS_BL_MOD_EN);
                lvds_gen_cntl |= (radeon_bl_get_level_brightness(pdata, 0) <<
                                  LVDS_BL_MOD_LEVEL_SHIFT);
                lvds_gen_cntl |= LVDS_DISPLAY_DIS;
                OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
                udelay(100);
                lvds_gen_cntl &= ~(LVDS_ON | LVDS_EN);
                OUTREG(LVDS_GEN_CNTL, lvds_gen_cntl);
                lvds_gen_cntl &= ~(LVDS_DIGON);
                rinfo->pending_lvds_gen_cntl = lvds_gen_cntl;
                mod_timer(&rinfo->lvds_timer,
                          jiffies + msecs_to_jiffies(rinfo->panel_info.pwr_delay));
                if (rinfo->is_mobility || rinfo->is_IGP)
                        OUTPLL(PIXCLKS_CNTL, tmpPixclksCntl);
        }
        rinfo->init_state.lvds_gen_cntl &= ~LVDS_STATE_MASK;
        rinfo->init_state.lvds_gen_cntl |= (lvds_gen_cntl & LVDS_STATE_MASK);

        return 0;
}

static const struct backlight_ops radeon_bl_data = {
        .update_status  = radeon_bl_update_status,
};

void radeonfb_bl_init(struct radeonfb_info *rinfo)
{
        struct backlight_properties props;
        struct backlight_device *bd;
        struct radeon_bl_privdata *pdata;
        char name[12];

        if (rinfo->mon1_type != MT_LCD)
                return;

#ifdef CONFIG_PMAC_BACKLIGHT
        if (!pmac_has_backlight_type("ati") &&
            !pmac_has_backlight_type("mnca"))
                return;
#endif

        pdata = kmalloc_obj(struct radeon_bl_privdata);
        if (!pdata) {
                printk("radeonfb: Memory allocation failed\n");
                goto error;
        }

        snprintf(name, sizeof(name), "radeonbl%d", rinfo->info->node);

        memset(&props, 0, sizeof(struct backlight_properties));
        props.type = BACKLIGHT_RAW;
        props.max_brightness = FB_BACKLIGHT_LEVELS - 1;
        bd = backlight_device_register(name, rinfo->info->device, pdata,
                                       &radeon_bl_data, &props);
        if (IS_ERR(bd)) {
                rinfo->info->bl_dev = NULL;
                printk("radeonfb: Backlight registration failed\n");
                goto error;
        }

        pdata->rinfo = rinfo;

        /* Pardon me for that hack... maybe some day we can figure out in what
         * direction backlight should work on a given panel?
         */
        pdata->negative =
                (rinfo->family != CHIP_FAMILY_RV200 &&
                 rinfo->family != CHIP_FAMILY_RV250 &&
                 rinfo->family != CHIP_FAMILY_RV280 &&
                 rinfo->family != CHIP_FAMILY_RV350);

#ifdef CONFIG_PMAC_BACKLIGHT
        pdata->negative = pdata->negative ||
                of_machine_is_compatible("PowerBook4,3") ||
                of_machine_is_compatible("PowerBook6,3") ||
                of_machine_is_compatible("PowerBook6,5");
#endif

        rinfo->info->bl_dev = bd;
        fb_bl_default_curve(rinfo->info, 0,
                 63 * FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL,
                217 * FB_BACKLIGHT_MAX / MAX_RADEON_LEVEL);

        bd->props.brightness = bd->props.max_brightness;
        bd->props.power = BACKLIGHT_POWER_ON;
        backlight_update_status(bd);

        printk("radeonfb: Backlight initialized (%s)\n", name);

        return;

error:
        kfree(pdata);
        return;
}

void radeonfb_bl_exit(struct radeonfb_info *rinfo)
{
        struct backlight_device *bd = rinfo->info->bl_dev;

        if (bd) {
                struct radeon_bl_privdata *pdata;

                pdata = bl_get_data(bd);
                backlight_device_unregister(bd);
                kfree(pdata);
                rinfo->info->bl_dev = NULL;

                printk("radeonfb: Backlight unloaded\n");
        }
}