162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Backlight code for via-pmu
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
662306a36Sopenharmony_ci * Copyright (C) 2001-2002 Benjamin Herrenschmidt
762306a36Sopenharmony_ci * Copyright (C) 2006      Michael Hanselmann <linux-kernel@hansmi.ch>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <asm/ptrace.h>
1262306a36Sopenharmony_ci#include <linux/adb.h>
1362306a36Sopenharmony_ci#include <linux/pmu.h>
1462306a36Sopenharmony_ci#include <asm/backlight.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define MAX_PMU_LEVEL 0xFF
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic const struct backlight_ops pmu_backlight_data;
1962306a36Sopenharmony_cistatic DEFINE_SPINLOCK(pmu_backlight_lock);
2062306a36Sopenharmony_cistatic int sleeping, uses_pmu_bl;
2162306a36Sopenharmony_cistatic u8 bl_curve[FB_BACKLIGHT_LEVELS];
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic void pmu_backlight_init_curve(u8 off, u8 min, u8 max)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	int i, flat, count, range = (max - min);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	bl_curve[0] = off;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat)
3062306a36Sopenharmony_ci		bl_curve[flat] = min;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	count = FB_BACKLIGHT_LEVELS * 15 / 16;
3362306a36Sopenharmony_ci	for (i = 0; i < count; ++i)
3462306a36Sopenharmony_ci		bl_curve[flat + i] = min + (range * (i + 1) / count);
3562306a36Sopenharmony_ci}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic int pmu_backlight_curve_lookup(int value)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	int level = (FB_BACKLIGHT_LEVELS - 1);
4062306a36Sopenharmony_ci	int i, max = 0;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	/* Look for biggest value */
4362306a36Sopenharmony_ci	for (i = 0; i < FB_BACKLIGHT_LEVELS; i++)
4462306a36Sopenharmony_ci		max = max((int)bl_curve[i], max);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	/* Look for nearest value */
4762306a36Sopenharmony_ci	for (i = 0; i < FB_BACKLIGHT_LEVELS; i++) {
4862306a36Sopenharmony_ci		int diff = abs(bl_curve[i] - value);
4962306a36Sopenharmony_ci		if (diff < max) {
5062306a36Sopenharmony_ci			max = diff;
5162306a36Sopenharmony_ci			level = i;
5262306a36Sopenharmony_ci		}
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci	return level;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int pmu_backlight_get_level_brightness(int level)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	int pmulevel;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/* Get and convert the value */
6262306a36Sopenharmony_ci	pmulevel = bl_curve[level] * FB_BACKLIGHT_MAX / MAX_PMU_LEVEL;
6362306a36Sopenharmony_ci	if (pmulevel < 0)
6462306a36Sopenharmony_ci		pmulevel = 0;
6562306a36Sopenharmony_ci	else if (pmulevel > MAX_PMU_LEVEL)
6662306a36Sopenharmony_ci		pmulevel = MAX_PMU_LEVEL;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	return pmulevel;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic int __pmu_backlight_update_status(struct backlight_device *bd)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct adb_request req;
7462306a36Sopenharmony_ci	int level = backlight_get_brightness(bd);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (level > 0) {
7762306a36Sopenharmony_ci		int pmulevel = pmu_backlight_get_level_brightness(level);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT, pmulevel);
8062306a36Sopenharmony_ci		pmu_wait_complete(&req);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
8362306a36Sopenharmony_ci			PMU_POW_BACKLIGHT | PMU_POW_ON);
8462306a36Sopenharmony_ci		pmu_wait_complete(&req);
8562306a36Sopenharmony_ci	} else {
8662306a36Sopenharmony_ci		pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
8762306a36Sopenharmony_ci			PMU_POW_BACKLIGHT | PMU_POW_OFF);
8862306a36Sopenharmony_ci		pmu_wait_complete(&req);
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	return 0;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic int pmu_backlight_update_status(struct backlight_device *bd)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	unsigned long flags;
9762306a36Sopenharmony_ci	int rc = 0;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	spin_lock_irqsave(&pmu_backlight_lock, flags);
10062306a36Sopenharmony_ci	/* Don't update brightness when sleeping */
10162306a36Sopenharmony_ci	if (!sleeping)
10262306a36Sopenharmony_ci		rc = __pmu_backlight_update_status(bd);
10362306a36Sopenharmony_ci	spin_unlock_irqrestore(&pmu_backlight_lock, flags);
10462306a36Sopenharmony_ci	return rc;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic const struct backlight_ops pmu_backlight_data = {
10962306a36Sopenharmony_ci	.update_status	= pmu_backlight_update_status,
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci};
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci#ifdef CONFIG_PM
11462306a36Sopenharmony_civoid pmu_backlight_set_sleep(int sleep)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	unsigned long flags;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	spin_lock_irqsave(&pmu_backlight_lock, flags);
11962306a36Sopenharmony_ci	sleeping = sleep;
12062306a36Sopenharmony_ci	if (pmac_backlight && uses_pmu_bl) {
12162306a36Sopenharmony_ci		if (sleep) {
12262306a36Sopenharmony_ci			struct adb_request req;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci			pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
12562306a36Sopenharmony_ci				    PMU_POW_BACKLIGHT | PMU_POW_OFF);
12662306a36Sopenharmony_ci			pmu_wait_complete(&req);
12762306a36Sopenharmony_ci		} else
12862306a36Sopenharmony_ci			__pmu_backlight_update_status(pmac_backlight);
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci	spin_unlock_irqrestore(&pmu_backlight_lock, flags);
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci#endif /* CONFIG_PM */
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_civoid __init pmu_backlight_init(void)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	struct backlight_properties props;
13762306a36Sopenharmony_ci	struct backlight_device *bd;
13862306a36Sopenharmony_ci	char name[10];
13962306a36Sopenharmony_ci	int level, autosave;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/* Special case for the old PowerBook since I can't test on it */
14262306a36Sopenharmony_ci	autosave =
14362306a36Sopenharmony_ci		of_machine_is_compatible("AAPL,3400/2400") ||
14462306a36Sopenharmony_ci		of_machine_is_compatible("AAPL,3500");
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if (!autosave &&
14762306a36Sopenharmony_ci	    !pmac_has_backlight_type("pmu") &&
14862306a36Sopenharmony_ci	    !of_machine_is_compatible("AAPL,PowerBook1998") &&
14962306a36Sopenharmony_ci	    !of_machine_is_compatible("PowerBook1,1"))
15062306a36Sopenharmony_ci		return;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	snprintf(name, sizeof(name), "pmubl");
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	memset(&props, 0, sizeof(struct backlight_properties));
15562306a36Sopenharmony_ci	props.type = BACKLIGHT_PLATFORM;
15662306a36Sopenharmony_ci	props.max_brightness = FB_BACKLIGHT_LEVELS - 1;
15762306a36Sopenharmony_ci	bd = backlight_device_register(name, NULL, NULL, &pmu_backlight_data,
15862306a36Sopenharmony_ci				       &props);
15962306a36Sopenharmony_ci	if (IS_ERR(bd)) {
16062306a36Sopenharmony_ci		printk(KERN_ERR "PMU Backlight registration failed\n");
16162306a36Sopenharmony_ci		return;
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci	uses_pmu_bl = 1;
16462306a36Sopenharmony_ci	pmu_backlight_init_curve(0x7F, 0x46, 0x0E);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	level = bd->props.max_brightness;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	if (autosave) {
16962306a36Sopenharmony_ci		/* read autosaved value if available */
17062306a36Sopenharmony_ci		struct adb_request req;
17162306a36Sopenharmony_ci		pmu_request(&req, NULL, 2, 0xd9, 0);
17262306a36Sopenharmony_ci		pmu_wait_complete(&req);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci		level = pmu_backlight_curve_lookup(
17562306a36Sopenharmony_ci				(req.reply[0] >> 4) *
17662306a36Sopenharmony_ci				bd->props.max_brightness / 15);
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	bd->props.brightness = level;
18062306a36Sopenharmony_ci	bd->props.power = FB_BLANK_UNBLANK;
18162306a36Sopenharmony_ci	backlight_update_status(bd);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	printk(KERN_INFO "PMU Backlight initialized (%s)\n", name);
18462306a36Sopenharmony_ci}
185