18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * vga_switcheroo.c - Support for laptop with dual GPU using one set of outputs
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (c) 2010 Red Hat Inc.
58c2ecf20Sopenharmony_ci * Author : Dave Airlie <airlied@redhat.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (c) 2015 Lukas Wunner <lukas@wunner.de>
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a
108c2ecf20Sopenharmony_ci * copy of this software and associated documentation files (the "Software"),
118c2ecf20Sopenharmony_ci * to deal in the Software without restriction, including without limitation
128c2ecf20Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense,
138c2ecf20Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the
148c2ecf20Sopenharmony_ci * Software is furnished to do so, subject to the following conditions:
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * The above copyright notice and this permission notice (including the next
178c2ecf20Sopenharmony_ci * paragraph) shall be included in all copies or substantial portions of the
188c2ecf20Sopenharmony_ci * Software.
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
218c2ecf20Sopenharmony_ci * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
228c2ecf20Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
238c2ecf20Sopenharmony_ci * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
248c2ecf20Sopenharmony_ci * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
258c2ecf20Sopenharmony_ci * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
268c2ecf20Sopenharmony_ci * DEALINGS
278c2ecf20Sopenharmony_ci * IN THE SOFTWARE.
288c2ecf20Sopenharmony_ci *
298c2ecf20Sopenharmony_ci */
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "vga_switcheroo: " fmt
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#include <linux/apple-gmux.h>
348c2ecf20Sopenharmony_ci#include <linux/console.h>
358c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
368c2ecf20Sopenharmony_ci#include <linux/fb.h>
378c2ecf20Sopenharmony_ci#include <linux/fs.h>
388c2ecf20Sopenharmony_ci#include <linux/fbcon.h>
398c2ecf20Sopenharmony_ci#include <linux/module.h>
408c2ecf20Sopenharmony_ci#include <linux/pci.h>
418c2ecf20Sopenharmony_ci#include <linux/pm_domain.h>
428c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h>
438c2ecf20Sopenharmony_ci#include <linux/seq_file.h>
448c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
458c2ecf20Sopenharmony_ci#include <linux/vgaarb.h>
468c2ecf20Sopenharmony_ci#include <linux/vga_switcheroo.h>
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/**
498c2ecf20Sopenharmony_ci * DOC: Overview
508c2ecf20Sopenharmony_ci *
518c2ecf20Sopenharmony_ci * vga_switcheroo is the Linux subsystem for laptop hybrid graphics.
528c2ecf20Sopenharmony_ci * These come in two flavors:
538c2ecf20Sopenharmony_ci *
548c2ecf20Sopenharmony_ci * * muxed: Dual GPUs with a multiplexer chip to switch outputs between GPUs.
558c2ecf20Sopenharmony_ci * * muxless: Dual GPUs but only one of them is connected to outputs.
568c2ecf20Sopenharmony_ci *   The other one is merely used to offload rendering, its results
578c2ecf20Sopenharmony_ci *   are copied over PCIe into the framebuffer. On Linux this is
588c2ecf20Sopenharmony_ci *   supported with DRI PRIME.
598c2ecf20Sopenharmony_ci *
608c2ecf20Sopenharmony_ci * Hybrid graphics started to appear in the late Naughties and were initially
618c2ecf20Sopenharmony_ci * all muxed. Newer laptops moved to a muxless architecture for cost reasons.
628c2ecf20Sopenharmony_ci * A notable exception is the MacBook Pro which continues to use a mux.
638c2ecf20Sopenharmony_ci * Muxes come with varying capabilities: Some switch only the panel, others
648c2ecf20Sopenharmony_ci * can also switch external displays. Some switch all display pins at once
658c2ecf20Sopenharmony_ci * while others can switch just the DDC lines. (To allow EDID probing
668c2ecf20Sopenharmony_ci * for the inactive GPU.) Also, muxes are often used to cut power to the
678c2ecf20Sopenharmony_ci * discrete GPU while it is not used.
688c2ecf20Sopenharmony_ci *
698c2ecf20Sopenharmony_ci * DRM drivers register GPUs with vga_switcheroo, these are henceforth called
708c2ecf20Sopenharmony_ci * clients. The mux is called the handler. Muxless machines also register a
718c2ecf20Sopenharmony_ci * handler to control the power state of the discrete GPU, its ->switchto
728c2ecf20Sopenharmony_ci * callback is a no-op for obvious reasons. The discrete GPU is often equipped
738c2ecf20Sopenharmony_ci * with an HDA controller for the HDMI/DP audio signal, this will also
748c2ecf20Sopenharmony_ci * register as a client so that vga_switcheroo can take care of the correct
758c2ecf20Sopenharmony_ci * suspend/resume order when changing the discrete GPU's power state. In total
768c2ecf20Sopenharmony_ci * there can thus be up to three clients: Two vga clients (GPUs) and one audio
778c2ecf20Sopenharmony_ci * client (on the discrete GPU). The code is mostly prepared to support
788c2ecf20Sopenharmony_ci * machines with more than two GPUs should they become available.
798c2ecf20Sopenharmony_ci *
808c2ecf20Sopenharmony_ci * The GPU to which the outputs are currently switched is called the
818c2ecf20Sopenharmony_ci * active client in vga_switcheroo parlance. The GPU not in use is the
828c2ecf20Sopenharmony_ci * inactive client. When the inactive client's DRM driver is loaded,
838c2ecf20Sopenharmony_ci * it will be unable to probe the panel's EDID and hence depends on
848c2ecf20Sopenharmony_ci * VBIOS to provide its display modes. If the VBIOS modes are bogus or
858c2ecf20Sopenharmony_ci * if there is no VBIOS at all (which is common on the MacBook Pro),
868c2ecf20Sopenharmony_ci * a client may alternatively request that the DDC lines are temporarily
878c2ecf20Sopenharmony_ci * switched to it, provided that the handler supports this. Switching
888c2ecf20Sopenharmony_ci * only the DDC lines and not the entire output avoids unnecessary
898c2ecf20Sopenharmony_ci * flickering.
908c2ecf20Sopenharmony_ci */
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci/**
938c2ecf20Sopenharmony_ci * struct vga_switcheroo_client - registered client
948c2ecf20Sopenharmony_ci * @pdev: client pci device
958c2ecf20Sopenharmony_ci * @fb_info: framebuffer to which console is remapped on switching
968c2ecf20Sopenharmony_ci * @pwr_state: current power state if manual power control is used.
978c2ecf20Sopenharmony_ci *	For driver power control, call vga_switcheroo_pwr_state().
988c2ecf20Sopenharmony_ci * @ops: client callbacks
998c2ecf20Sopenharmony_ci * @id: client identifier. Determining the id requires the handler,
1008c2ecf20Sopenharmony_ci *	so gpus are initially assigned VGA_SWITCHEROO_UNKNOWN_ID
1018c2ecf20Sopenharmony_ci *	and later given their true id in vga_switcheroo_enable()
1028c2ecf20Sopenharmony_ci * @active: whether the outputs are currently switched to this client
1038c2ecf20Sopenharmony_ci * @driver_power_control: whether power state is controlled by the driver's
1048c2ecf20Sopenharmony_ci *	runtime pm. If true, writing ON and OFF to the vga_switcheroo debugfs
1058c2ecf20Sopenharmony_ci *	interface is a no-op so as not to interfere with runtime pm
1068c2ecf20Sopenharmony_ci * @list: client list
1078c2ecf20Sopenharmony_ci * @vga_dev: pci device, indicate which GPU is bound to current audio client
1088c2ecf20Sopenharmony_ci *
1098c2ecf20Sopenharmony_ci * Registered client. A client can be either a GPU or an audio device on a GPU.
1108c2ecf20Sopenharmony_ci * For audio clients, the @fb_info and @active members are bogus. For GPU
1118c2ecf20Sopenharmony_ci * clients, the @vga_dev is bogus.
1128c2ecf20Sopenharmony_ci */
1138c2ecf20Sopenharmony_cistruct vga_switcheroo_client {
1148c2ecf20Sopenharmony_ci	struct pci_dev *pdev;
1158c2ecf20Sopenharmony_ci	struct fb_info *fb_info;
1168c2ecf20Sopenharmony_ci	enum vga_switcheroo_state pwr_state;
1178c2ecf20Sopenharmony_ci	const struct vga_switcheroo_client_ops *ops;
1188c2ecf20Sopenharmony_ci	enum vga_switcheroo_client_id id;
1198c2ecf20Sopenharmony_ci	bool active;
1208c2ecf20Sopenharmony_ci	bool driver_power_control;
1218c2ecf20Sopenharmony_ci	struct list_head list;
1228c2ecf20Sopenharmony_ci	struct pci_dev *vga_dev;
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci/*
1268c2ecf20Sopenharmony_ci * protects access to struct vgasr_priv
1278c2ecf20Sopenharmony_ci */
1288c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(vgasr_mutex);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci/**
1318c2ecf20Sopenharmony_ci * struct vgasr_priv - vga_switcheroo private data
1328c2ecf20Sopenharmony_ci * @active: whether vga_switcheroo is enabled.
1338c2ecf20Sopenharmony_ci *	Prerequisite is the registration of two GPUs and a handler
1348c2ecf20Sopenharmony_ci * @delayed_switch_active: whether a delayed switch is pending
1358c2ecf20Sopenharmony_ci * @delayed_client_id: client to which a delayed switch is pending
1368c2ecf20Sopenharmony_ci * @debugfs_root: directory for vga_switcheroo debugfs interface
1378c2ecf20Sopenharmony_ci * @registered_clients: number of registered GPUs
1388c2ecf20Sopenharmony_ci *	(counting only vga clients, not audio clients)
1398c2ecf20Sopenharmony_ci * @clients: list of registered clients
1408c2ecf20Sopenharmony_ci * @handler: registered handler
1418c2ecf20Sopenharmony_ci * @handler_flags: flags of registered handler
1428c2ecf20Sopenharmony_ci * @mux_hw_lock: protects mux state
1438c2ecf20Sopenharmony_ci *	(in particular while DDC lines are temporarily switched)
1448c2ecf20Sopenharmony_ci * @old_ddc_owner: client to which DDC lines will be switched back on unlock
1458c2ecf20Sopenharmony_ci *
1468c2ecf20Sopenharmony_ci * vga_switcheroo private data. Currently only one vga_switcheroo instance
1478c2ecf20Sopenharmony_ci * per system is supported.
1488c2ecf20Sopenharmony_ci */
1498c2ecf20Sopenharmony_cistruct vgasr_priv {
1508c2ecf20Sopenharmony_ci	bool active;
1518c2ecf20Sopenharmony_ci	bool delayed_switch_active;
1528c2ecf20Sopenharmony_ci	enum vga_switcheroo_client_id delayed_client_id;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	struct dentry *debugfs_root;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	int registered_clients;
1578c2ecf20Sopenharmony_ci	struct list_head clients;
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	const struct vga_switcheroo_handler *handler;
1608c2ecf20Sopenharmony_ci	enum vga_switcheroo_handler_flags_t handler_flags;
1618c2ecf20Sopenharmony_ci	struct mutex mux_hw_lock;
1628c2ecf20Sopenharmony_ci	int old_ddc_owner;
1638c2ecf20Sopenharmony_ci};
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci#define ID_BIT_AUDIO		0x100
1668c2ecf20Sopenharmony_ci#define client_is_audio(c)		((c)->id & ID_BIT_AUDIO)
1678c2ecf20Sopenharmony_ci#define client_is_vga(c)		(!client_is_audio(c))
1688c2ecf20Sopenharmony_ci#define client_id(c)		((c)->id & ~ID_BIT_AUDIO)
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic void vga_switcheroo_debugfs_init(struct vgasr_priv *priv);
1718c2ecf20Sopenharmony_cistatic void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci/* only one switcheroo per system */
1748c2ecf20Sopenharmony_cistatic struct vgasr_priv vgasr_priv = {
1758c2ecf20Sopenharmony_ci	.clients = LIST_HEAD_INIT(vgasr_priv.clients),
1768c2ecf20Sopenharmony_ci	.mux_hw_lock = __MUTEX_INITIALIZER(vgasr_priv.mux_hw_lock),
1778c2ecf20Sopenharmony_ci};
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_cistatic bool vga_switcheroo_ready(void)
1808c2ecf20Sopenharmony_ci{
1818c2ecf20Sopenharmony_ci	/* we're ready if we get two clients + handler */
1828c2ecf20Sopenharmony_ci	return !vgasr_priv.active &&
1838c2ecf20Sopenharmony_ci	       vgasr_priv.registered_clients == 2 && vgasr_priv.handler;
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic void vga_switcheroo_enable(void)
1878c2ecf20Sopenharmony_ci{
1888c2ecf20Sopenharmony_ci	int ret;
1898c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	/* call the handler to init */
1928c2ecf20Sopenharmony_ci	if (vgasr_priv.handler->init)
1938c2ecf20Sopenharmony_ci		vgasr_priv.handler->init();
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	list_for_each_entry(client, &vgasr_priv.clients, list) {
1968c2ecf20Sopenharmony_ci		if (!client_is_vga(client) ||
1978c2ecf20Sopenharmony_ci		     client_id(client) != VGA_SWITCHEROO_UNKNOWN_ID)
1988c2ecf20Sopenharmony_ci			continue;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci		ret = vgasr_priv.handler->get_client_id(client->pdev);
2018c2ecf20Sopenharmony_ci		if (ret < 0)
2028c2ecf20Sopenharmony_ci			return;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci		client->id = ret;
2058c2ecf20Sopenharmony_ci	}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	list_for_each_entry(client, &vgasr_priv.clients, list) {
2088c2ecf20Sopenharmony_ci		if (!client_is_audio(client) ||
2098c2ecf20Sopenharmony_ci		     client_id(client) != VGA_SWITCHEROO_UNKNOWN_ID)
2108c2ecf20Sopenharmony_ci			continue;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		ret = vgasr_priv.handler->get_client_id(client->vga_dev);
2138c2ecf20Sopenharmony_ci		if (ret < 0)
2148c2ecf20Sopenharmony_ci			return;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci		client->id = ret | ID_BIT_AUDIO;
2178c2ecf20Sopenharmony_ci		if (client->ops->gpu_bound)
2188c2ecf20Sopenharmony_ci			client->ops->gpu_bound(client->pdev, ret);
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	vga_switcheroo_debugfs_init(&vgasr_priv);
2228c2ecf20Sopenharmony_ci	vgasr_priv.active = true;
2238c2ecf20Sopenharmony_ci}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci/**
2268c2ecf20Sopenharmony_ci * vga_switcheroo_register_handler() - register handler
2278c2ecf20Sopenharmony_ci * @handler: handler callbacks
2288c2ecf20Sopenharmony_ci * @handler_flags: handler flags
2298c2ecf20Sopenharmony_ci *
2308c2ecf20Sopenharmony_ci * Register handler. Enable vga_switcheroo if two vga clients have already
2318c2ecf20Sopenharmony_ci * registered.
2328c2ecf20Sopenharmony_ci *
2338c2ecf20Sopenharmony_ci * Return: 0 on success, -EINVAL if a handler was already registered.
2348c2ecf20Sopenharmony_ci */
2358c2ecf20Sopenharmony_ciint vga_switcheroo_register_handler(
2368c2ecf20Sopenharmony_ci			  const struct vga_switcheroo_handler *handler,
2378c2ecf20Sopenharmony_ci			  enum vga_switcheroo_handler_flags_t handler_flags)
2388c2ecf20Sopenharmony_ci{
2398c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
2408c2ecf20Sopenharmony_ci	if (vgasr_priv.handler) {
2418c2ecf20Sopenharmony_ci		mutex_unlock(&vgasr_mutex);
2428c2ecf20Sopenharmony_ci		return -EINVAL;
2438c2ecf20Sopenharmony_ci	}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	vgasr_priv.handler = handler;
2468c2ecf20Sopenharmony_ci	vgasr_priv.handler_flags = handler_flags;
2478c2ecf20Sopenharmony_ci	if (vga_switcheroo_ready()) {
2488c2ecf20Sopenharmony_ci		pr_info("enabled\n");
2498c2ecf20Sopenharmony_ci		vga_switcheroo_enable();
2508c2ecf20Sopenharmony_ci	}
2518c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
2528c2ecf20Sopenharmony_ci	return 0;
2538c2ecf20Sopenharmony_ci}
2548c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_register_handler);
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci/**
2578c2ecf20Sopenharmony_ci * vga_switcheroo_unregister_handler() - unregister handler
2588c2ecf20Sopenharmony_ci *
2598c2ecf20Sopenharmony_ci * Unregister handler. Disable vga_switcheroo.
2608c2ecf20Sopenharmony_ci */
2618c2ecf20Sopenharmony_civoid vga_switcheroo_unregister_handler(void)
2628c2ecf20Sopenharmony_ci{
2638c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
2648c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_priv.mux_hw_lock);
2658c2ecf20Sopenharmony_ci	vgasr_priv.handler_flags = 0;
2668c2ecf20Sopenharmony_ci	vgasr_priv.handler = NULL;
2678c2ecf20Sopenharmony_ci	if (vgasr_priv.active) {
2688c2ecf20Sopenharmony_ci		pr_info("disabled\n");
2698c2ecf20Sopenharmony_ci		vga_switcheroo_debugfs_fini(&vgasr_priv);
2708c2ecf20Sopenharmony_ci		vgasr_priv.active = false;
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_priv.mux_hw_lock);
2738c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_unregister_handler);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci/**
2788c2ecf20Sopenharmony_ci * vga_switcheroo_handler_flags() - obtain handler flags
2798c2ecf20Sopenharmony_ci *
2808c2ecf20Sopenharmony_ci * Helper for clients to obtain the handler flags bitmask.
2818c2ecf20Sopenharmony_ci *
2828c2ecf20Sopenharmony_ci * Return: Handler flags. A value of 0 means that no handler is registered
2838c2ecf20Sopenharmony_ci * or that the handler has no special capabilities.
2848c2ecf20Sopenharmony_ci */
2858c2ecf20Sopenharmony_cienum vga_switcheroo_handler_flags_t vga_switcheroo_handler_flags(void)
2868c2ecf20Sopenharmony_ci{
2878c2ecf20Sopenharmony_ci	return vgasr_priv.handler_flags;
2888c2ecf20Sopenharmony_ci}
2898c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_handler_flags);
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_cistatic int register_client(struct pci_dev *pdev,
2928c2ecf20Sopenharmony_ci			   const struct vga_switcheroo_client_ops *ops,
2938c2ecf20Sopenharmony_ci			   enum vga_switcheroo_client_id id,
2948c2ecf20Sopenharmony_ci			   struct pci_dev *vga_dev,
2958c2ecf20Sopenharmony_ci			   bool active,
2968c2ecf20Sopenharmony_ci			   bool driver_power_control)
2978c2ecf20Sopenharmony_ci{
2988c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	client = kzalloc(sizeof(*client), GFP_KERNEL);
3018c2ecf20Sopenharmony_ci	if (!client)
3028c2ecf20Sopenharmony_ci		return -ENOMEM;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	client->pwr_state = VGA_SWITCHEROO_ON;
3058c2ecf20Sopenharmony_ci	client->pdev = pdev;
3068c2ecf20Sopenharmony_ci	client->ops = ops;
3078c2ecf20Sopenharmony_ci	client->id = id;
3088c2ecf20Sopenharmony_ci	client->active = active;
3098c2ecf20Sopenharmony_ci	client->driver_power_control = driver_power_control;
3108c2ecf20Sopenharmony_ci	client->vga_dev = vga_dev;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
3138c2ecf20Sopenharmony_ci	list_add_tail(&client->list, &vgasr_priv.clients);
3148c2ecf20Sopenharmony_ci	if (client_is_vga(client))
3158c2ecf20Sopenharmony_ci		vgasr_priv.registered_clients++;
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	if (vga_switcheroo_ready()) {
3188c2ecf20Sopenharmony_ci		pr_info("enabled\n");
3198c2ecf20Sopenharmony_ci		vga_switcheroo_enable();
3208c2ecf20Sopenharmony_ci	}
3218c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
3228c2ecf20Sopenharmony_ci	return 0;
3238c2ecf20Sopenharmony_ci}
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci/**
3268c2ecf20Sopenharmony_ci * vga_switcheroo_register_client - register vga client
3278c2ecf20Sopenharmony_ci * @pdev: client pci device
3288c2ecf20Sopenharmony_ci * @ops: client callbacks
3298c2ecf20Sopenharmony_ci * @driver_power_control: whether power state is controlled by the driver's
3308c2ecf20Sopenharmony_ci *	runtime pm
3318c2ecf20Sopenharmony_ci *
3328c2ecf20Sopenharmony_ci * Register vga client (GPU). Enable vga_switcheroo if another GPU and a
3338c2ecf20Sopenharmony_ci * handler have already registered. The power state of the client is assumed
3348c2ecf20Sopenharmony_ci * to be ON. Beforehand, vga_switcheroo_client_probe_defer() shall be called
3358c2ecf20Sopenharmony_ci * to ensure that all prerequisites are met.
3368c2ecf20Sopenharmony_ci *
3378c2ecf20Sopenharmony_ci * Return: 0 on success, -ENOMEM on memory allocation error.
3388c2ecf20Sopenharmony_ci */
3398c2ecf20Sopenharmony_ciint vga_switcheroo_register_client(struct pci_dev *pdev,
3408c2ecf20Sopenharmony_ci				   const struct vga_switcheroo_client_ops *ops,
3418c2ecf20Sopenharmony_ci				   bool driver_power_control)
3428c2ecf20Sopenharmony_ci{
3438c2ecf20Sopenharmony_ci	return register_client(pdev, ops, VGA_SWITCHEROO_UNKNOWN_ID, NULL,
3448c2ecf20Sopenharmony_ci			       pdev == vga_default_device(),
3458c2ecf20Sopenharmony_ci			       driver_power_control);
3468c2ecf20Sopenharmony_ci}
3478c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_register_client);
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci/**
3508c2ecf20Sopenharmony_ci * vga_switcheroo_register_audio_client - register audio client
3518c2ecf20Sopenharmony_ci * @pdev: client pci device
3528c2ecf20Sopenharmony_ci * @ops: client callbacks
3538c2ecf20Sopenharmony_ci * @vga_dev:  pci device which is bound to current audio client
3548c2ecf20Sopenharmony_ci *
3558c2ecf20Sopenharmony_ci * Register audio client (audio device on a GPU). The client is assumed
3568c2ecf20Sopenharmony_ci * to use runtime PM. Beforehand, vga_switcheroo_client_probe_defer()
3578c2ecf20Sopenharmony_ci * shall be called to ensure that all prerequisites are met.
3588c2ecf20Sopenharmony_ci *
3598c2ecf20Sopenharmony_ci * Return: 0 on success, -ENOMEM on memory allocation error, -EINVAL on getting
3608c2ecf20Sopenharmony_ci * client id error.
3618c2ecf20Sopenharmony_ci */
3628c2ecf20Sopenharmony_ciint vga_switcheroo_register_audio_client(struct pci_dev *pdev,
3638c2ecf20Sopenharmony_ci			const struct vga_switcheroo_client_ops *ops,
3648c2ecf20Sopenharmony_ci			struct pci_dev *vga_dev)
3658c2ecf20Sopenharmony_ci{
3668c2ecf20Sopenharmony_ci	enum vga_switcheroo_client_id id = VGA_SWITCHEROO_UNKNOWN_ID;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	/*
3698c2ecf20Sopenharmony_ci	 * if vga_switcheroo has enabled, that mean two GPU clients and also
3708c2ecf20Sopenharmony_ci	 * handler are registered. Get audio client id from bound GPU client
3718c2ecf20Sopenharmony_ci	 * id directly, otherwise, set it as VGA_SWITCHEROO_UNKNOWN_ID,
3728c2ecf20Sopenharmony_ci	 * it will set to correct id in later when vga_switcheroo_enable()
3738c2ecf20Sopenharmony_ci	 * is called.
3748c2ecf20Sopenharmony_ci	 */
3758c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
3768c2ecf20Sopenharmony_ci	if (vgasr_priv.active) {
3778c2ecf20Sopenharmony_ci		id = vgasr_priv.handler->get_client_id(vga_dev);
3788c2ecf20Sopenharmony_ci		if (id < 0) {
3798c2ecf20Sopenharmony_ci			mutex_unlock(&vgasr_mutex);
3808c2ecf20Sopenharmony_ci			return -EINVAL;
3818c2ecf20Sopenharmony_ci		}
3828c2ecf20Sopenharmony_ci		/* notify if GPU has been already bound */
3838c2ecf20Sopenharmony_ci		if (ops->gpu_bound)
3848c2ecf20Sopenharmony_ci			ops->gpu_bound(pdev, id);
3858c2ecf20Sopenharmony_ci	}
3868c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	return register_client(pdev, ops, id | ID_BIT_AUDIO, vga_dev,
3898c2ecf20Sopenharmony_ci			       false, true);
3908c2ecf20Sopenharmony_ci}
3918c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_register_audio_client);
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_cistatic struct vga_switcheroo_client *
3948c2ecf20Sopenharmony_cifind_client_from_pci(struct list_head *head, struct pci_dev *pdev)
3958c2ecf20Sopenharmony_ci{
3968c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci	list_for_each_entry(client, head, list)
3998c2ecf20Sopenharmony_ci		if (client->pdev == pdev)
4008c2ecf20Sopenharmony_ci			return client;
4018c2ecf20Sopenharmony_ci	return NULL;
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic struct vga_switcheroo_client *
4058c2ecf20Sopenharmony_cifind_client_from_id(struct list_head *head,
4068c2ecf20Sopenharmony_ci		    enum vga_switcheroo_client_id client_id)
4078c2ecf20Sopenharmony_ci{
4088c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci	list_for_each_entry(client, head, list)
4118c2ecf20Sopenharmony_ci		if (client->id == client_id)
4128c2ecf20Sopenharmony_ci			return client;
4138c2ecf20Sopenharmony_ci	return NULL;
4148c2ecf20Sopenharmony_ci}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_cistatic struct vga_switcheroo_client *
4178c2ecf20Sopenharmony_cifind_active_client(struct list_head *head)
4188c2ecf20Sopenharmony_ci{
4198c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	list_for_each_entry(client, head, list)
4228c2ecf20Sopenharmony_ci		if (client->active)
4238c2ecf20Sopenharmony_ci			return client;
4248c2ecf20Sopenharmony_ci	return NULL;
4258c2ecf20Sopenharmony_ci}
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci/**
4288c2ecf20Sopenharmony_ci * vga_switcheroo_client_probe_defer() - whether to defer probing a given client
4298c2ecf20Sopenharmony_ci * @pdev: client pci device
4308c2ecf20Sopenharmony_ci *
4318c2ecf20Sopenharmony_ci * Determine whether any prerequisites are not fulfilled to probe a given
4328c2ecf20Sopenharmony_ci * client. Drivers shall invoke this early on in their ->probe callback
4338c2ecf20Sopenharmony_ci * and return %-EPROBE_DEFER if it evaluates to %true. Thou shalt not
4348c2ecf20Sopenharmony_ci * register the client ere thou hast called this.
4358c2ecf20Sopenharmony_ci *
4368c2ecf20Sopenharmony_ci * Return: %true if probing should be deferred, otherwise %false.
4378c2ecf20Sopenharmony_ci */
4388c2ecf20Sopenharmony_cibool vga_switcheroo_client_probe_defer(struct pci_dev *pdev)
4398c2ecf20Sopenharmony_ci{
4408c2ecf20Sopenharmony_ci	if ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) {
4418c2ecf20Sopenharmony_ci		/*
4428c2ecf20Sopenharmony_ci		 * apple-gmux is needed on pre-retina MacBook Pro
4438c2ecf20Sopenharmony_ci		 * to probe the panel if pdev is the inactive GPU.
4448c2ecf20Sopenharmony_ci		 */
4458c2ecf20Sopenharmony_ci		if (apple_gmux_present() && pdev != vga_default_device() &&
4468c2ecf20Sopenharmony_ci		    !vgasr_priv.handler_flags)
4478c2ecf20Sopenharmony_ci			return true;
4488c2ecf20Sopenharmony_ci	}
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	return false;
4518c2ecf20Sopenharmony_ci}
4528c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_client_probe_defer);
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_cistatic enum vga_switcheroo_state
4558c2ecf20Sopenharmony_civga_switcheroo_pwr_state(struct vga_switcheroo_client *client)
4568c2ecf20Sopenharmony_ci{
4578c2ecf20Sopenharmony_ci	if (client->driver_power_control)
4588c2ecf20Sopenharmony_ci		if (pm_runtime_enabled(&client->pdev->dev) &&
4598c2ecf20Sopenharmony_ci		    pm_runtime_active(&client->pdev->dev))
4608c2ecf20Sopenharmony_ci			return VGA_SWITCHEROO_ON;
4618c2ecf20Sopenharmony_ci		else
4628c2ecf20Sopenharmony_ci			return VGA_SWITCHEROO_OFF;
4638c2ecf20Sopenharmony_ci	else
4648c2ecf20Sopenharmony_ci		return client->pwr_state;
4658c2ecf20Sopenharmony_ci}
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci/**
4688c2ecf20Sopenharmony_ci * vga_switcheroo_get_client_state() - obtain power state of a given client
4698c2ecf20Sopenharmony_ci * @pdev: client pci device
4708c2ecf20Sopenharmony_ci *
4718c2ecf20Sopenharmony_ci * Obtain power state of a given client as seen from vga_switcheroo.
4728c2ecf20Sopenharmony_ci * The function is only called from hda_intel.c.
4738c2ecf20Sopenharmony_ci *
4748c2ecf20Sopenharmony_ci * Return: Power state.
4758c2ecf20Sopenharmony_ci */
4768c2ecf20Sopenharmony_cienum vga_switcheroo_state vga_switcheroo_get_client_state(struct pci_dev *pdev)
4778c2ecf20Sopenharmony_ci{
4788c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
4798c2ecf20Sopenharmony_ci	enum vga_switcheroo_state ret;
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
4828c2ecf20Sopenharmony_ci	client = find_client_from_pci(&vgasr_priv.clients, pdev);
4838c2ecf20Sopenharmony_ci	if (!client)
4848c2ecf20Sopenharmony_ci		ret = VGA_SWITCHEROO_NOT_FOUND;
4858c2ecf20Sopenharmony_ci	else
4868c2ecf20Sopenharmony_ci		ret = vga_switcheroo_pwr_state(client);
4878c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
4888c2ecf20Sopenharmony_ci	return ret;
4898c2ecf20Sopenharmony_ci}
4908c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_get_client_state);
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_ci/**
4938c2ecf20Sopenharmony_ci * vga_switcheroo_unregister_client() - unregister client
4948c2ecf20Sopenharmony_ci * @pdev: client pci device
4958c2ecf20Sopenharmony_ci *
4968c2ecf20Sopenharmony_ci * Unregister client. Disable vga_switcheroo if this is a vga client (GPU).
4978c2ecf20Sopenharmony_ci */
4988c2ecf20Sopenharmony_civoid vga_switcheroo_unregister_client(struct pci_dev *pdev)
4998c2ecf20Sopenharmony_ci{
5008c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
5038c2ecf20Sopenharmony_ci	client = find_client_from_pci(&vgasr_priv.clients, pdev);
5048c2ecf20Sopenharmony_ci	if (client) {
5058c2ecf20Sopenharmony_ci		if (client_is_vga(client))
5068c2ecf20Sopenharmony_ci			vgasr_priv.registered_clients--;
5078c2ecf20Sopenharmony_ci		list_del(&client->list);
5088c2ecf20Sopenharmony_ci		kfree(client);
5098c2ecf20Sopenharmony_ci	}
5108c2ecf20Sopenharmony_ci	if (vgasr_priv.active && vgasr_priv.registered_clients < 2) {
5118c2ecf20Sopenharmony_ci		pr_info("disabled\n");
5128c2ecf20Sopenharmony_ci		vga_switcheroo_debugfs_fini(&vgasr_priv);
5138c2ecf20Sopenharmony_ci		vgasr_priv.active = false;
5148c2ecf20Sopenharmony_ci	}
5158c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
5168c2ecf20Sopenharmony_ci}
5178c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_unregister_client);
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci/**
5208c2ecf20Sopenharmony_ci * vga_switcheroo_client_fb_set() - set framebuffer of a given client
5218c2ecf20Sopenharmony_ci * @pdev: client pci device
5228c2ecf20Sopenharmony_ci * @info: framebuffer
5238c2ecf20Sopenharmony_ci *
5248c2ecf20Sopenharmony_ci * Set framebuffer of a given client. The console will be remapped to this
5258c2ecf20Sopenharmony_ci * on switching.
5268c2ecf20Sopenharmony_ci */
5278c2ecf20Sopenharmony_civoid vga_switcheroo_client_fb_set(struct pci_dev *pdev,
5288c2ecf20Sopenharmony_ci				 struct fb_info *info)
5298c2ecf20Sopenharmony_ci{
5308c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
5338c2ecf20Sopenharmony_ci	client = find_client_from_pci(&vgasr_priv.clients, pdev);
5348c2ecf20Sopenharmony_ci	if (client)
5358c2ecf20Sopenharmony_ci		client->fb_info = info;
5368c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
5378c2ecf20Sopenharmony_ci}
5388c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_client_fb_set);
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_ci/**
5418c2ecf20Sopenharmony_ci * vga_switcheroo_lock_ddc() - temporarily switch DDC lines to a given client
5428c2ecf20Sopenharmony_ci * @pdev: client pci device
5438c2ecf20Sopenharmony_ci *
5448c2ecf20Sopenharmony_ci * Temporarily switch DDC lines to the client identified by @pdev
5458c2ecf20Sopenharmony_ci * (but leave the outputs otherwise switched to where they are).
5468c2ecf20Sopenharmony_ci * This allows the inactive client to probe EDID. The DDC lines must
5478c2ecf20Sopenharmony_ci * afterwards be switched back by calling vga_switcheroo_unlock_ddc(),
5488c2ecf20Sopenharmony_ci * even if this function returns an error.
5498c2ecf20Sopenharmony_ci *
5508c2ecf20Sopenharmony_ci * Return: Previous DDC owner on success or a negative int on error.
5518c2ecf20Sopenharmony_ci * Specifically, %-ENODEV if no handler has registered or if the handler
5528c2ecf20Sopenharmony_ci * does not support switching the DDC lines. Also, a negative value
5538c2ecf20Sopenharmony_ci * returned by the handler is propagated back to the caller.
5548c2ecf20Sopenharmony_ci * The return value has merely an informational purpose for any caller
5558c2ecf20Sopenharmony_ci * which might be interested in it. It is acceptable to ignore the return
5568c2ecf20Sopenharmony_ci * value and simply rely on the result of the subsequent EDID probe,
5578c2ecf20Sopenharmony_ci * which will be %NULL if DDC switching failed.
5588c2ecf20Sopenharmony_ci */
5598c2ecf20Sopenharmony_ciint vga_switcheroo_lock_ddc(struct pci_dev *pdev)
5608c2ecf20Sopenharmony_ci{
5618c2ecf20Sopenharmony_ci	enum vga_switcheroo_client_id id;
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_priv.mux_hw_lock);
5648c2ecf20Sopenharmony_ci	if (!vgasr_priv.handler || !vgasr_priv.handler->switch_ddc) {
5658c2ecf20Sopenharmony_ci		vgasr_priv.old_ddc_owner = -ENODEV;
5668c2ecf20Sopenharmony_ci		return -ENODEV;
5678c2ecf20Sopenharmony_ci	}
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	id = vgasr_priv.handler->get_client_id(pdev);
5708c2ecf20Sopenharmony_ci	vgasr_priv.old_ddc_owner = vgasr_priv.handler->switch_ddc(id);
5718c2ecf20Sopenharmony_ci	return vgasr_priv.old_ddc_owner;
5728c2ecf20Sopenharmony_ci}
5738c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_lock_ddc);
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_ci/**
5768c2ecf20Sopenharmony_ci * vga_switcheroo_unlock_ddc() - switch DDC lines back to previous owner
5778c2ecf20Sopenharmony_ci * @pdev: client pci device
5788c2ecf20Sopenharmony_ci *
5798c2ecf20Sopenharmony_ci * Switch DDC lines back to the previous owner after calling
5808c2ecf20Sopenharmony_ci * vga_switcheroo_lock_ddc(). This must be called even if
5818c2ecf20Sopenharmony_ci * vga_switcheroo_lock_ddc() returned an error.
5828c2ecf20Sopenharmony_ci *
5838c2ecf20Sopenharmony_ci * Return: Previous DDC owner on success (i.e. the client identifier of @pdev)
5848c2ecf20Sopenharmony_ci * or a negative int on error.
5858c2ecf20Sopenharmony_ci * Specifically, %-ENODEV if no handler has registered or if the handler
5868c2ecf20Sopenharmony_ci * does not support switching the DDC lines. Also, a negative value
5878c2ecf20Sopenharmony_ci * returned by the handler is propagated back to the caller.
5888c2ecf20Sopenharmony_ci * Finally, invoking this function without calling vga_switcheroo_lock_ddc()
5898c2ecf20Sopenharmony_ci * first is not allowed and will result in %-EINVAL.
5908c2ecf20Sopenharmony_ci */
5918c2ecf20Sopenharmony_ciint vga_switcheroo_unlock_ddc(struct pci_dev *pdev)
5928c2ecf20Sopenharmony_ci{
5938c2ecf20Sopenharmony_ci	enum vga_switcheroo_client_id id;
5948c2ecf20Sopenharmony_ci	int ret = vgasr_priv.old_ddc_owner;
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_ci	if (WARN_ON_ONCE(!mutex_is_locked(&vgasr_priv.mux_hw_lock)))
5978c2ecf20Sopenharmony_ci		return -EINVAL;
5988c2ecf20Sopenharmony_ci
5998c2ecf20Sopenharmony_ci	if (vgasr_priv.old_ddc_owner >= 0) {
6008c2ecf20Sopenharmony_ci		id = vgasr_priv.handler->get_client_id(pdev);
6018c2ecf20Sopenharmony_ci		if (vgasr_priv.old_ddc_owner != id)
6028c2ecf20Sopenharmony_ci			ret = vgasr_priv.handler->switch_ddc(
6038c2ecf20Sopenharmony_ci						     vgasr_priv.old_ddc_owner);
6048c2ecf20Sopenharmony_ci	}
6058c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_priv.mux_hw_lock);
6068c2ecf20Sopenharmony_ci	return ret;
6078c2ecf20Sopenharmony_ci}
6088c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_unlock_ddc);
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci/**
6118c2ecf20Sopenharmony_ci * DOC: Manual switching and manual power control
6128c2ecf20Sopenharmony_ci *
6138c2ecf20Sopenharmony_ci * In this mode of use, the file /sys/kernel/debug/vgaswitcheroo/switch
6148c2ecf20Sopenharmony_ci * can be read to retrieve the current vga_switcheroo state and commands
6158c2ecf20Sopenharmony_ci * can be written to it to change the state. The file appears as soon as
6168c2ecf20Sopenharmony_ci * two GPU drivers and one handler have registered with vga_switcheroo.
6178c2ecf20Sopenharmony_ci * The following commands are understood:
6188c2ecf20Sopenharmony_ci *
6198c2ecf20Sopenharmony_ci * * OFF: Power off the device not in use.
6208c2ecf20Sopenharmony_ci * * ON: Power on the device not in use.
6218c2ecf20Sopenharmony_ci * * IGD: Switch to the integrated graphics device.
6228c2ecf20Sopenharmony_ci *   Power on the integrated GPU if necessary, power off the discrete GPU.
6238c2ecf20Sopenharmony_ci *   Prerequisite is that no user space processes (e.g. Xorg, alsactl)
6248c2ecf20Sopenharmony_ci *   have opened device files of the GPUs or the audio client. If the
6258c2ecf20Sopenharmony_ci *   switch fails, the user may invoke lsof(8) or fuser(1) on /dev/dri/
6268c2ecf20Sopenharmony_ci *   and /dev/snd/controlC1 to identify processes blocking the switch.
6278c2ecf20Sopenharmony_ci * * DIS: Switch to the discrete graphics device.
6288c2ecf20Sopenharmony_ci * * DIGD: Delayed switch to the integrated graphics device.
6298c2ecf20Sopenharmony_ci *   This will perform the switch once the last user space process has
6308c2ecf20Sopenharmony_ci *   closed the device files of the GPUs and the audio client.
6318c2ecf20Sopenharmony_ci * * DDIS: Delayed switch to the discrete graphics device.
6328c2ecf20Sopenharmony_ci * * MIGD: Mux-only switch to the integrated graphics device.
6338c2ecf20Sopenharmony_ci *   Does not remap console or change the power state of either gpu.
6348c2ecf20Sopenharmony_ci *   If the integrated GPU is currently off, the screen will turn black.
6358c2ecf20Sopenharmony_ci *   If it is on, the screen will show whatever happens to be in VRAM.
6368c2ecf20Sopenharmony_ci *   Either way, the user has to blindly enter the command to switch back.
6378c2ecf20Sopenharmony_ci * * MDIS: Mux-only switch to the discrete graphics device.
6388c2ecf20Sopenharmony_ci *
6398c2ecf20Sopenharmony_ci * For GPUs whose power state is controlled by the driver's runtime pm,
6408c2ecf20Sopenharmony_ci * the ON and OFF commands are a no-op (see next section).
6418c2ecf20Sopenharmony_ci *
6428c2ecf20Sopenharmony_ci * For muxless machines, the IGD/DIS, DIGD/DDIS and MIGD/MDIS commands
6438c2ecf20Sopenharmony_ci * should not be used.
6448c2ecf20Sopenharmony_ci */
6458c2ecf20Sopenharmony_ci
6468c2ecf20Sopenharmony_cistatic int vga_switcheroo_show(struct seq_file *m, void *v)
6478c2ecf20Sopenharmony_ci{
6488c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
6498c2ecf20Sopenharmony_ci	int i = 0;
6508c2ecf20Sopenharmony_ci
6518c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
6528c2ecf20Sopenharmony_ci	list_for_each_entry(client, &vgasr_priv.clients, list) {
6538c2ecf20Sopenharmony_ci		seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i,
6548c2ecf20Sopenharmony_ci			   client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" :
6558c2ecf20Sopenharmony_ci								     "IGD",
6568c2ecf20Sopenharmony_ci			   client_is_vga(client) ? "" : "-Audio",
6578c2ecf20Sopenharmony_ci			   client->active ? '+' : ' ',
6588c2ecf20Sopenharmony_ci			   client->driver_power_control ? "Dyn" : "",
6598c2ecf20Sopenharmony_ci			   vga_switcheroo_pwr_state(client) ? "Pwr" : "Off",
6608c2ecf20Sopenharmony_ci			   pci_name(client->pdev));
6618c2ecf20Sopenharmony_ci		i++;
6628c2ecf20Sopenharmony_ci	}
6638c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
6648c2ecf20Sopenharmony_ci	return 0;
6658c2ecf20Sopenharmony_ci}
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_cistatic int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
6688c2ecf20Sopenharmony_ci{
6698c2ecf20Sopenharmony_ci	return single_open(file, vga_switcheroo_show, NULL);
6708c2ecf20Sopenharmony_ci}
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_cistatic int vga_switchon(struct vga_switcheroo_client *client)
6738c2ecf20Sopenharmony_ci{
6748c2ecf20Sopenharmony_ci	if (client->driver_power_control)
6758c2ecf20Sopenharmony_ci		return 0;
6768c2ecf20Sopenharmony_ci	if (vgasr_priv.handler->power_state)
6778c2ecf20Sopenharmony_ci		vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON);
6788c2ecf20Sopenharmony_ci	/* call the driver callback to turn on device */
6798c2ecf20Sopenharmony_ci	client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON);
6808c2ecf20Sopenharmony_ci	client->pwr_state = VGA_SWITCHEROO_ON;
6818c2ecf20Sopenharmony_ci	return 0;
6828c2ecf20Sopenharmony_ci}
6838c2ecf20Sopenharmony_ci
6848c2ecf20Sopenharmony_cistatic int vga_switchoff(struct vga_switcheroo_client *client)
6858c2ecf20Sopenharmony_ci{
6868c2ecf20Sopenharmony_ci	if (client->driver_power_control)
6878c2ecf20Sopenharmony_ci		return 0;
6888c2ecf20Sopenharmony_ci	/* call the driver callback to turn off device */
6898c2ecf20Sopenharmony_ci	client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF);
6908c2ecf20Sopenharmony_ci	if (vgasr_priv.handler->power_state)
6918c2ecf20Sopenharmony_ci		vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF);
6928c2ecf20Sopenharmony_ci	client->pwr_state = VGA_SWITCHEROO_OFF;
6938c2ecf20Sopenharmony_ci	return 0;
6948c2ecf20Sopenharmony_ci}
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_cistatic void set_audio_state(enum vga_switcheroo_client_id id,
6978c2ecf20Sopenharmony_ci			    enum vga_switcheroo_state state)
6988c2ecf20Sopenharmony_ci{
6998c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_ci	client = find_client_from_id(&vgasr_priv.clients, id | ID_BIT_AUDIO);
7028c2ecf20Sopenharmony_ci	if (client)
7038c2ecf20Sopenharmony_ci		client->ops->set_gpu_state(client->pdev, state);
7048c2ecf20Sopenharmony_ci}
7058c2ecf20Sopenharmony_ci
7068c2ecf20Sopenharmony_ci/* stage one happens before delay */
7078c2ecf20Sopenharmony_cistatic int vga_switchto_stage1(struct vga_switcheroo_client *new_client)
7088c2ecf20Sopenharmony_ci{
7098c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *active;
7108c2ecf20Sopenharmony_ci
7118c2ecf20Sopenharmony_ci	active = find_active_client(&vgasr_priv.clients);
7128c2ecf20Sopenharmony_ci	if (!active)
7138c2ecf20Sopenharmony_ci		return 0;
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	if (vga_switcheroo_pwr_state(new_client) == VGA_SWITCHEROO_OFF)
7168c2ecf20Sopenharmony_ci		vga_switchon(new_client);
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_ci	vga_set_default_device(new_client->pdev);
7198c2ecf20Sopenharmony_ci	return 0;
7208c2ecf20Sopenharmony_ci}
7218c2ecf20Sopenharmony_ci
7228c2ecf20Sopenharmony_ci/* post delay */
7238c2ecf20Sopenharmony_cistatic int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
7248c2ecf20Sopenharmony_ci{
7258c2ecf20Sopenharmony_ci	int ret;
7268c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *active;
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_ci	active = find_active_client(&vgasr_priv.clients);
7298c2ecf20Sopenharmony_ci	if (!active)
7308c2ecf20Sopenharmony_ci		return 0;
7318c2ecf20Sopenharmony_ci
7328c2ecf20Sopenharmony_ci	active->active = false;
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ci	/* let HDA controller autosuspend if GPU uses driver power control */
7358c2ecf20Sopenharmony_ci	if (!active->driver_power_control)
7368c2ecf20Sopenharmony_ci		set_audio_state(active->id, VGA_SWITCHEROO_OFF);
7378c2ecf20Sopenharmony_ci
7388c2ecf20Sopenharmony_ci	if (new_client->fb_info)
7398c2ecf20Sopenharmony_ci		fbcon_remap_all(new_client->fb_info);
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_priv.mux_hw_lock);
7428c2ecf20Sopenharmony_ci	ret = vgasr_priv.handler->switchto(new_client->id);
7438c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_priv.mux_hw_lock);
7448c2ecf20Sopenharmony_ci	if (ret)
7458c2ecf20Sopenharmony_ci		return ret;
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_ci	if (new_client->ops->reprobe)
7488c2ecf20Sopenharmony_ci		new_client->ops->reprobe(new_client->pdev);
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_ci	if (vga_switcheroo_pwr_state(active) == VGA_SWITCHEROO_ON)
7518c2ecf20Sopenharmony_ci		vga_switchoff(active);
7528c2ecf20Sopenharmony_ci
7538c2ecf20Sopenharmony_ci	/* let HDA controller autoresume if GPU uses driver power control */
7548c2ecf20Sopenharmony_ci	if (!new_client->driver_power_control)
7558c2ecf20Sopenharmony_ci		set_audio_state(new_client->id, VGA_SWITCHEROO_ON);
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci	new_client->active = true;
7588c2ecf20Sopenharmony_ci	return 0;
7598c2ecf20Sopenharmony_ci}
7608c2ecf20Sopenharmony_ci
7618c2ecf20Sopenharmony_cistatic bool check_can_switch(void)
7628c2ecf20Sopenharmony_ci{
7638c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
7648c2ecf20Sopenharmony_ci
7658c2ecf20Sopenharmony_ci	list_for_each_entry(client, &vgasr_priv.clients, list) {
7668c2ecf20Sopenharmony_ci		if (!client->ops->can_switch(client->pdev)) {
7678c2ecf20Sopenharmony_ci			pr_err("client %x refused switch\n", client->id);
7688c2ecf20Sopenharmony_ci			return false;
7698c2ecf20Sopenharmony_ci		}
7708c2ecf20Sopenharmony_ci	}
7718c2ecf20Sopenharmony_ci	return true;
7728c2ecf20Sopenharmony_ci}
7738c2ecf20Sopenharmony_ci
7748c2ecf20Sopenharmony_cistatic ssize_t
7758c2ecf20Sopenharmony_civga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf,
7768c2ecf20Sopenharmony_ci			     size_t cnt, loff_t *ppos)
7778c2ecf20Sopenharmony_ci{
7788c2ecf20Sopenharmony_ci	char usercmd[64];
7798c2ecf20Sopenharmony_ci	int ret;
7808c2ecf20Sopenharmony_ci	bool delay = false, can_switch;
7818c2ecf20Sopenharmony_ci	bool just_mux = false;
7828c2ecf20Sopenharmony_ci	enum vga_switcheroo_client_id client_id = VGA_SWITCHEROO_UNKNOWN_ID;
7838c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client = NULL;
7848c2ecf20Sopenharmony_ci
7858c2ecf20Sopenharmony_ci	if (cnt > 63)
7868c2ecf20Sopenharmony_ci		cnt = 63;
7878c2ecf20Sopenharmony_ci
7888c2ecf20Sopenharmony_ci	if (copy_from_user(usercmd, ubuf, cnt))
7898c2ecf20Sopenharmony_ci		return -EFAULT;
7908c2ecf20Sopenharmony_ci
7918c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
7928c2ecf20Sopenharmony_ci
7938c2ecf20Sopenharmony_ci	if (!vgasr_priv.active) {
7948c2ecf20Sopenharmony_ci		cnt = -EINVAL;
7958c2ecf20Sopenharmony_ci		goto out;
7968c2ecf20Sopenharmony_ci	}
7978c2ecf20Sopenharmony_ci
7988c2ecf20Sopenharmony_ci	/* pwr off the device not in use */
7998c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "OFF", 3) == 0) {
8008c2ecf20Sopenharmony_ci		list_for_each_entry(client, &vgasr_priv.clients, list) {
8018c2ecf20Sopenharmony_ci			if (client->active || client_is_audio(client))
8028c2ecf20Sopenharmony_ci				continue;
8038c2ecf20Sopenharmony_ci			if (client->driver_power_control)
8048c2ecf20Sopenharmony_ci				continue;
8058c2ecf20Sopenharmony_ci			set_audio_state(client->id, VGA_SWITCHEROO_OFF);
8068c2ecf20Sopenharmony_ci			if (client->pwr_state == VGA_SWITCHEROO_ON)
8078c2ecf20Sopenharmony_ci				vga_switchoff(client);
8088c2ecf20Sopenharmony_ci		}
8098c2ecf20Sopenharmony_ci		goto out;
8108c2ecf20Sopenharmony_ci	}
8118c2ecf20Sopenharmony_ci	/* pwr on the device not in use */
8128c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "ON", 2) == 0) {
8138c2ecf20Sopenharmony_ci		list_for_each_entry(client, &vgasr_priv.clients, list) {
8148c2ecf20Sopenharmony_ci			if (client->active || client_is_audio(client))
8158c2ecf20Sopenharmony_ci				continue;
8168c2ecf20Sopenharmony_ci			if (client->driver_power_control)
8178c2ecf20Sopenharmony_ci				continue;
8188c2ecf20Sopenharmony_ci			if (client->pwr_state == VGA_SWITCHEROO_OFF)
8198c2ecf20Sopenharmony_ci				vga_switchon(client);
8208c2ecf20Sopenharmony_ci			set_audio_state(client->id, VGA_SWITCHEROO_ON);
8218c2ecf20Sopenharmony_ci		}
8228c2ecf20Sopenharmony_ci		goto out;
8238c2ecf20Sopenharmony_ci	}
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci	/* request a delayed switch - test can we switch now */
8268c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "DIGD", 4) == 0) {
8278c2ecf20Sopenharmony_ci		client_id = VGA_SWITCHEROO_IGD;
8288c2ecf20Sopenharmony_ci		delay = true;
8298c2ecf20Sopenharmony_ci	}
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "DDIS", 4) == 0) {
8328c2ecf20Sopenharmony_ci		client_id = VGA_SWITCHEROO_DIS;
8338c2ecf20Sopenharmony_ci		delay = true;
8348c2ecf20Sopenharmony_ci	}
8358c2ecf20Sopenharmony_ci
8368c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "IGD", 3) == 0)
8378c2ecf20Sopenharmony_ci		client_id = VGA_SWITCHEROO_IGD;
8388c2ecf20Sopenharmony_ci
8398c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "DIS", 3) == 0)
8408c2ecf20Sopenharmony_ci		client_id = VGA_SWITCHEROO_DIS;
8418c2ecf20Sopenharmony_ci
8428c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "MIGD", 4) == 0) {
8438c2ecf20Sopenharmony_ci		just_mux = true;
8448c2ecf20Sopenharmony_ci		client_id = VGA_SWITCHEROO_IGD;
8458c2ecf20Sopenharmony_ci	}
8468c2ecf20Sopenharmony_ci	if (strncmp(usercmd, "MDIS", 4) == 0) {
8478c2ecf20Sopenharmony_ci		just_mux = true;
8488c2ecf20Sopenharmony_ci		client_id = VGA_SWITCHEROO_DIS;
8498c2ecf20Sopenharmony_ci	}
8508c2ecf20Sopenharmony_ci
8518c2ecf20Sopenharmony_ci	if (client_id == VGA_SWITCHEROO_UNKNOWN_ID)
8528c2ecf20Sopenharmony_ci		goto out;
8538c2ecf20Sopenharmony_ci	client = find_client_from_id(&vgasr_priv.clients, client_id);
8548c2ecf20Sopenharmony_ci	if (!client)
8558c2ecf20Sopenharmony_ci		goto out;
8568c2ecf20Sopenharmony_ci
8578c2ecf20Sopenharmony_ci	vgasr_priv.delayed_switch_active = false;
8588c2ecf20Sopenharmony_ci
8598c2ecf20Sopenharmony_ci	if (just_mux) {
8608c2ecf20Sopenharmony_ci		mutex_lock(&vgasr_priv.mux_hw_lock);
8618c2ecf20Sopenharmony_ci		ret = vgasr_priv.handler->switchto(client_id);
8628c2ecf20Sopenharmony_ci		mutex_unlock(&vgasr_priv.mux_hw_lock);
8638c2ecf20Sopenharmony_ci		goto out;
8648c2ecf20Sopenharmony_ci	}
8658c2ecf20Sopenharmony_ci
8668c2ecf20Sopenharmony_ci	if (client->active)
8678c2ecf20Sopenharmony_ci		goto out;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	/* okay we want a switch - test if devices are willing to switch */
8708c2ecf20Sopenharmony_ci	can_switch = check_can_switch();
8718c2ecf20Sopenharmony_ci
8728c2ecf20Sopenharmony_ci	if (can_switch == false && delay == false)
8738c2ecf20Sopenharmony_ci		goto out;
8748c2ecf20Sopenharmony_ci
8758c2ecf20Sopenharmony_ci	if (can_switch) {
8768c2ecf20Sopenharmony_ci		ret = vga_switchto_stage1(client);
8778c2ecf20Sopenharmony_ci		if (ret)
8788c2ecf20Sopenharmony_ci			pr_err("switching failed stage 1 %d\n", ret);
8798c2ecf20Sopenharmony_ci
8808c2ecf20Sopenharmony_ci		ret = vga_switchto_stage2(client);
8818c2ecf20Sopenharmony_ci		if (ret)
8828c2ecf20Sopenharmony_ci			pr_err("switching failed stage 2 %d\n", ret);
8838c2ecf20Sopenharmony_ci
8848c2ecf20Sopenharmony_ci	} else {
8858c2ecf20Sopenharmony_ci		pr_info("setting delayed switch to client %d\n", client->id);
8868c2ecf20Sopenharmony_ci		vgasr_priv.delayed_switch_active = true;
8878c2ecf20Sopenharmony_ci		vgasr_priv.delayed_client_id = client_id;
8888c2ecf20Sopenharmony_ci
8898c2ecf20Sopenharmony_ci		ret = vga_switchto_stage1(client);
8908c2ecf20Sopenharmony_ci		if (ret)
8918c2ecf20Sopenharmony_ci			pr_err("delayed switching stage 1 failed %d\n", ret);
8928c2ecf20Sopenharmony_ci	}
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ciout:
8958c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
8968c2ecf20Sopenharmony_ci	return cnt;
8978c2ecf20Sopenharmony_ci}
8988c2ecf20Sopenharmony_ci
8998c2ecf20Sopenharmony_cistatic const struct file_operations vga_switcheroo_debugfs_fops = {
9008c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
9018c2ecf20Sopenharmony_ci	.open = vga_switcheroo_debugfs_open,
9028c2ecf20Sopenharmony_ci	.write = vga_switcheroo_debugfs_write,
9038c2ecf20Sopenharmony_ci	.read = seq_read,
9048c2ecf20Sopenharmony_ci	.llseek = seq_lseek,
9058c2ecf20Sopenharmony_ci	.release = single_release,
9068c2ecf20Sopenharmony_ci};
9078c2ecf20Sopenharmony_ci
9088c2ecf20Sopenharmony_cistatic void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv)
9098c2ecf20Sopenharmony_ci{
9108c2ecf20Sopenharmony_ci	debugfs_remove_recursive(priv->debugfs_root);
9118c2ecf20Sopenharmony_ci	priv->debugfs_root = NULL;
9128c2ecf20Sopenharmony_ci}
9138c2ecf20Sopenharmony_ci
9148c2ecf20Sopenharmony_cistatic void vga_switcheroo_debugfs_init(struct vgasr_priv *priv)
9158c2ecf20Sopenharmony_ci{
9168c2ecf20Sopenharmony_ci	/* already initialised */
9178c2ecf20Sopenharmony_ci	if (priv->debugfs_root)
9188c2ecf20Sopenharmony_ci		return;
9198c2ecf20Sopenharmony_ci
9208c2ecf20Sopenharmony_ci	priv->debugfs_root = debugfs_create_dir("vgaswitcheroo", NULL);
9218c2ecf20Sopenharmony_ci
9228c2ecf20Sopenharmony_ci	debugfs_create_file("switch", 0644, priv->debugfs_root, NULL,
9238c2ecf20Sopenharmony_ci			    &vga_switcheroo_debugfs_fops);
9248c2ecf20Sopenharmony_ci}
9258c2ecf20Sopenharmony_ci
9268c2ecf20Sopenharmony_ci/**
9278c2ecf20Sopenharmony_ci * vga_switcheroo_process_delayed_switch() - helper for delayed switching
9288c2ecf20Sopenharmony_ci *
9298c2ecf20Sopenharmony_ci * Process a delayed switch if one is pending. DRM drivers should call this
9308c2ecf20Sopenharmony_ci * from their ->lastclose callback.
9318c2ecf20Sopenharmony_ci *
9328c2ecf20Sopenharmony_ci * Return: 0 on success. -EINVAL if no delayed switch is pending, if the client
9338c2ecf20Sopenharmony_ci * has unregistered in the meantime or if there are other clients blocking the
9348c2ecf20Sopenharmony_ci * switch. If the actual switch fails, an error is reported and 0 is returned.
9358c2ecf20Sopenharmony_ci */
9368c2ecf20Sopenharmony_ciint vga_switcheroo_process_delayed_switch(void)
9378c2ecf20Sopenharmony_ci{
9388c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
9398c2ecf20Sopenharmony_ci	int ret;
9408c2ecf20Sopenharmony_ci	int err = -EINVAL;
9418c2ecf20Sopenharmony_ci
9428c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
9438c2ecf20Sopenharmony_ci	if (!vgasr_priv.delayed_switch_active)
9448c2ecf20Sopenharmony_ci		goto err;
9458c2ecf20Sopenharmony_ci
9468c2ecf20Sopenharmony_ci	pr_info("processing delayed switch to %d\n",
9478c2ecf20Sopenharmony_ci		vgasr_priv.delayed_client_id);
9488c2ecf20Sopenharmony_ci
9498c2ecf20Sopenharmony_ci	client = find_client_from_id(&vgasr_priv.clients,
9508c2ecf20Sopenharmony_ci				     vgasr_priv.delayed_client_id);
9518c2ecf20Sopenharmony_ci	if (!client || !check_can_switch())
9528c2ecf20Sopenharmony_ci		goto err;
9538c2ecf20Sopenharmony_ci
9548c2ecf20Sopenharmony_ci	ret = vga_switchto_stage2(client);
9558c2ecf20Sopenharmony_ci	if (ret)
9568c2ecf20Sopenharmony_ci		pr_err("delayed switching failed stage 2 %d\n", ret);
9578c2ecf20Sopenharmony_ci
9588c2ecf20Sopenharmony_ci	vgasr_priv.delayed_switch_active = false;
9598c2ecf20Sopenharmony_ci	err = 0;
9608c2ecf20Sopenharmony_cierr:
9618c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
9628c2ecf20Sopenharmony_ci	return err;
9638c2ecf20Sopenharmony_ci}
9648c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_process_delayed_switch);
9658c2ecf20Sopenharmony_ci
9668c2ecf20Sopenharmony_ci/**
9678c2ecf20Sopenharmony_ci * DOC: Driver power control
9688c2ecf20Sopenharmony_ci *
9698c2ecf20Sopenharmony_ci * In this mode of use, the discrete GPU automatically powers up and down at
9708c2ecf20Sopenharmony_ci * the discretion of the driver's runtime pm. On muxed machines, the user may
9718c2ecf20Sopenharmony_ci * still influence the muxer state by way of the debugfs interface, however
9728c2ecf20Sopenharmony_ci * the ON and OFF commands become a no-op for the discrete GPU.
9738c2ecf20Sopenharmony_ci *
9748c2ecf20Sopenharmony_ci * This mode is the default on Nvidia HybridPower/Optimus and ATI PowerXpress.
9758c2ecf20Sopenharmony_ci * Specifying nouveau.runpm=0, radeon.runpm=0 or amdgpu.runpm=0 on the kernel
9768c2ecf20Sopenharmony_ci * command line disables it.
9778c2ecf20Sopenharmony_ci *
9788c2ecf20Sopenharmony_ci * After the GPU has been suspended, the handler needs to be called to cut
9798c2ecf20Sopenharmony_ci * power to the GPU. Likewise it needs to reinstate power before the GPU
9808c2ecf20Sopenharmony_ci * can resume. This is achieved by vga_switcheroo_init_domain_pm_ops(),
9818c2ecf20Sopenharmony_ci * which augments the GPU's suspend/resume functions by the requisite
9828c2ecf20Sopenharmony_ci * calls to the handler.
9838c2ecf20Sopenharmony_ci *
9848c2ecf20Sopenharmony_ci * When the audio device resumes, the GPU needs to be woken. This is achieved
9858c2ecf20Sopenharmony_ci * by a PCI quirk which calls device_link_add() to declare a dependency on the
9868c2ecf20Sopenharmony_ci * GPU. That way, the GPU is kept awake whenever and as long as the audio
9878c2ecf20Sopenharmony_ci * device is in use.
9888c2ecf20Sopenharmony_ci *
9898c2ecf20Sopenharmony_ci * On muxed machines, if the mux is initially switched to the discrete GPU,
9908c2ecf20Sopenharmony_ci * the user ends up with a black screen when the GPU powers down after boot.
9918c2ecf20Sopenharmony_ci * As a workaround, the mux is forced to the integrated GPU on runtime suspend,
9928c2ecf20Sopenharmony_ci * cf. https://bugs.freedesktop.org/show_bug.cgi?id=75917
9938c2ecf20Sopenharmony_ci */
9948c2ecf20Sopenharmony_ci
9958c2ecf20Sopenharmony_cistatic void vga_switcheroo_power_switch(struct pci_dev *pdev,
9968c2ecf20Sopenharmony_ci					enum vga_switcheroo_state state)
9978c2ecf20Sopenharmony_ci{
9988c2ecf20Sopenharmony_ci	struct vga_switcheroo_client *client;
9998c2ecf20Sopenharmony_ci
10008c2ecf20Sopenharmony_ci	if (!vgasr_priv.handler->power_state)
10018c2ecf20Sopenharmony_ci		return;
10028c2ecf20Sopenharmony_ci
10038c2ecf20Sopenharmony_ci	client = find_client_from_pci(&vgasr_priv.clients, pdev);
10048c2ecf20Sopenharmony_ci	if (!client)
10058c2ecf20Sopenharmony_ci		return;
10068c2ecf20Sopenharmony_ci
10078c2ecf20Sopenharmony_ci	if (!client->driver_power_control)
10088c2ecf20Sopenharmony_ci		return;
10098c2ecf20Sopenharmony_ci
10108c2ecf20Sopenharmony_ci	vgasr_priv.handler->power_state(client->id, state);
10118c2ecf20Sopenharmony_ci}
10128c2ecf20Sopenharmony_ci
10138c2ecf20Sopenharmony_ci/* switcheroo power domain */
10148c2ecf20Sopenharmony_cistatic int vga_switcheroo_runtime_suspend(struct device *dev)
10158c2ecf20Sopenharmony_ci{
10168c2ecf20Sopenharmony_ci	struct pci_dev *pdev = to_pci_dev(dev);
10178c2ecf20Sopenharmony_ci	int ret;
10188c2ecf20Sopenharmony_ci
10198c2ecf20Sopenharmony_ci	ret = dev->bus->pm->runtime_suspend(dev);
10208c2ecf20Sopenharmony_ci	if (ret)
10218c2ecf20Sopenharmony_ci		return ret;
10228c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
10238c2ecf20Sopenharmony_ci	if (vgasr_priv.handler->switchto) {
10248c2ecf20Sopenharmony_ci		mutex_lock(&vgasr_priv.mux_hw_lock);
10258c2ecf20Sopenharmony_ci		vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD);
10268c2ecf20Sopenharmony_ci		mutex_unlock(&vgasr_priv.mux_hw_lock);
10278c2ecf20Sopenharmony_ci	}
10288c2ecf20Sopenharmony_ci	pci_bus_set_current_state(pdev->bus, PCI_D3cold);
10298c2ecf20Sopenharmony_ci	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF);
10308c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
10318c2ecf20Sopenharmony_ci	return 0;
10328c2ecf20Sopenharmony_ci}
10338c2ecf20Sopenharmony_ci
10348c2ecf20Sopenharmony_cistatic int vga_switcheroo_runtime_resume(struct device *dev)
10358c2ecf20Sopenharmony_ci{
10368c2ecf20Sopenharmony_ci	struct pci_dev *pdev = to_pci_dev(dev);
10378c2ecf20Sopenharmony_ci	int ret;
10388c2ecf20Sopenharmony_ci
10398c2ecf20Sopenharmony_ci	mutex_lock(&vgasr_mutex);
10408c2ecf20Sopenharmony_ci	vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_ON);
10418c2ecf20Sopenharmony_ci	mutex_unlock(&vgasr_mutex);
10428c2ecf20Sopenharmony_ci	pci_wakeup_bus(pdev->bus);
10438c2ecf20Sopenharmony_ci	ret = dev->bus->pm->runtime_resume(dev);
10448c2ecf20Sopenharmony_ci	if (ret)
10458c2ecf20Sopenharmony_ci		return ret;
10468c2ecf20Sopenharmony_ci
10478c2ecf20Sopenharmony_ci	return 0;
10488c2ecf20Sopenharmony_ci}
10498c2ecf20Sopenharmony_ci
10508c2ecf20Sopenharmony_ci/**
10518c2ecf20Sopenharmony_ci * vga_switcheroo_init_domain_pm_ops() - helper for driver power control
10528c2ecf20Sopenharmony_ci * @dev: vga client device
10538c2ecf20Sopenharmony_ci * @domain: power domain
10548c2ecf20Sopenharmony_ci *
10558c2ecf20Sopenharmony_ci * Helper for GPUs whose power state is controlled by the driver's runtime pm.
10568c2ecf20Sopenharmony_ci * After the GPU has been suspended, the handler needs to be called to cut
10578c2ecf20Sopenharmony_ci * power to the GPU. Likewise it needs to reinstate power before the GPU
10588c2ecf20Sopenharmony_ci * can resume. To this end, this helper augments the suspend/resume functions
10598c2ecf20Sopenharmony_ci * by the requisite calls to the handler. It needs only be called on platforms
10608c2ecf20Sopenharmony_ci * where the power switch is separate to the device being powered down.
10618c2ecf20Sopenharmony_ci */
10628c2ecf20Sopenharmony_ciint vga_switcheroo_init_domain_pm_ops(struct device *dev,
10638c2ecf20Sopenharmony_ci				      struct dev_pm_domain *domain)
10648c2ecf20Sopenharmony_ci{
10658c2ecf20Sopenharmony_ci	/* copy over all the bus versions */
10668c2ecf20Sopenharmony_ci	if (dev->bus && dev->bus->pm) {
10678c2ecf20Sopenharmony_ci		domain->ops = *dev->bus->pm;
10688c2ecf20Sopenharmony_ci		domain->ops.runtime_suspend = vga_switcheroo_runtime_suspend;
10698c2ecf20Sopenharmony_ci		domain->ops.runtime_resume = vga_switcheroo_runtime_resume;
10708c2ecf20Sopenharmony_ci
10718c2ecf20Sopenharmony_ci		dev_pm_domain_set(dev, domain);
10728c2ecf20Sopenharmony_ci		return 0;
10738c2ecf20Sopenharmony_ci	}
10748c2ecf20Sopenharmony_ci	dev_pm_domain_set(dev, NULL);
10758c2ecf20Sopenharmony_ci	return -EINVAL;
10768c2ecf20Sopenharmony_ci}
10778c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_init_domain_pm_ops);
10788c2ecf20Sopenharmony_ci
10798c2ecf20Sopenharmony_civoid vga_switcheroo_fini_domain_pm_ops(struct device *dev)
10808c2ecf20Sopenharmony_ci{
10818c2ecf20Sopenharmony_ci	dev_pm_domain_set(dev, NULL);
10828c2ecf20Sopenharmony_ci}
10838c2ecf20Sopenharmony_ciEXPORT_SYMBOL(vga_switcheroo_fini_domain_pm_ops);
1084