18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *	Copyright (C) 2014 Linaro Ltd.
48c2ecf20Sopenharmony_ci *	Author:	Ashwin Chaugule <ashwin.chaugule@linaro.org>
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci *  PCC (Platform Communication Channel) is defined in the ACPI 5.0+
78c2ecf20Sopenharmony_ci *  specification. It is a mailbox like mechanism to allow clients
88c2ecf20Sopenharmony_ci *  such as CPPC (Collaborative Processor Performance Control), RAS
98c2ecf20Sopenharmony_ci *  (Reliability, Availability and Serviceability) and MPST (Memory
108c2ecf20Sopenharmony_ci *  Node Power State Table) to talk to the platform (e.g. BMC) through
118c2ecf20Sopenharmony_ci *  shared memory regions as defined in the PCC table entries. The PCC
128c2ecf20Sopenharmony_ci *  specification supports a Doorbell mechanism for the PCC clients
138c2ecf20Sopenharmony_ci *  to notify the platform about new data. This Doorbell information
148c2ecf20Sopenharmony_ci *  is also specified in each PCC table entry.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci *  Typical high level flow of operation is:
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci *  PCC Reads:
198c2ecf20Sopenharmony_ci *  * Client tries to acquire a channel lock.
208c2ecf20Sopenharmony_ci *  * After it is acquired it writes READ cmd in communication region cmd
218c2ecf20Sopenharmony_ci *		address.
228c2ecf20Sopenharmony_ci *  * Client issues mbox_send_message() which rings the PCC doorbell
238c2ecf20Sopenharmony_ci *		for its PCC channel.
248c2ecf20Sopenharmony_ci *  * If command completes, then client has control over channel and
258c2ecf20Sopenharmony_ci *		it can proceed with its reads.
268c2ecf20Sopenharmony_ci *  * Client releases lock.
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci *  PCC Writes:
298c2ecf20Sopenharmony_ci *  * Client tries to acquire channel lock.
308c2ecf20Sopenharmony_ci *  * Client writes to its communication region after it acquires a
318c2ecf20Sopenharmony_ci *		channel lock.
328c2ecf20Sopenharmony_ci *  * Client writes WRITE cmd in communication region cmd address.
338c2ecf20Sopenharmony_ci *  * Client issues mbox_send_message() which rings the PCC doorbell
348c2ecf20Sopenharmony_ci *		for its PCC channel.
358c2ecf20Sopenharmony_ci *  * If command completes, then writes have succeded and it can release
368c2ecf20Sopenharmony_ci *		the channel lock.
378c2ecf20Sopenharmony_ci *
388c2ecf20Sopenharmony_ci *  There is a Nominal latency defined for each channel which indicates
398c2ecf20Sopenharmony_ci *  how long to wait until a command completes. If command is not complete
408c2ecf20Sopenharmony_ci *  the client needs to retry or assume failure.
418c2ecf20Sopenharmony_ci *
428c2ecf20Sopenharmony_ci *	For more details about PCC, please see the ACPI specification from
438c2ecf20Sopenharmony_ci *  http://www.uefi.org/ACPIv5.1 Section 14.
448c2ecf20Sopenharmony_ci *
458c2ecf20Sopenharmony_ci *  This file implements PCC as a Mailbox controller and allows for PCC
468c2ecf20Sopenharmony_ci *  clients to be implemented as its Mailbox Client Channels.
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci#include <linux/acpi.h>
508c2ecf20Sopenharmony_ci#include <linux/delay.h>
518c2ecf20Sopenharmony_ci#include <linux/io.h>
528c2ecf20Sopenharmony_ci#include <linux/init.h>
538c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
548c2ecf20Sopenharmony_ci#include <linux/list.h>
558c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
568c2ecf20Sopenharmony_ci#include <linux/mailbox_controller.h>
578c2ecf20Sopenharmony_ci#include <linux/mailbox_client.h>
588c2ecf20Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h>
598c2ecf20Sopenharmony_ci#include <acpi/pcc.h>
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#include "mailbox.h"
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci#define MBOX_IRQ_NAME		"pcc-mbox"
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic struct mbox_chan *pcc_mbox_channels;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci/* Array of cached virtual address for doorbell registers */
688c2ecf20Sopenharmony_cistatic void __iomem **pcc_doorbell_vaddr;
698c2ecf20Sopenharmony_ci/* Array of cached virtual address for doorbell ack registers */
708c2ecf20Sopenharmony_cistatic void __iomem **pcc_doorbell_ack_vaddr;
718c2ecf20Sopenharmony_ci/* Array of doorbell interrupts */
728c2ecf20Sopenharmony_cistatic int *pcc_doorbell_irq;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic struct mbox_controller pcc_mbox_ctrl = {};
758c2ecf20Sopenharmony_ci/**
768c2ecf20Sopenharmony_ci * get_pcc_channel - Given a PCC subspace idx, get
778c2ecf20Sopenharmony_ci *	the respective mbox_channel.
788c2ecf20Sopenharmony_ci * @id: PCC subspace index.
798c2ecf20Sopenharmony_ci *
808c2ecf20Sopenharmony_ci * Return: ERR_PTR(errno) if error, else pointer
818c2ecf20Sopenharmony_ci *	to mbox channel.
828c2ecf20Sopenharmony_ci */
838c2ecf20Sopenharmony_cistatic struct mbox_chan *get_pcc_channel(int id)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	if (id < 0 || id >= pcc_mbox_ctrl.num_chans)
868c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOENT);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return &pcc_mbox_channels[id];
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci/*
928c2ecf20Sopenharmony_ci * PCC can be used with perf critical drivers such as CPPC
938c2ecf20Sopenharmony_ci * So it makes sense to locally cache the virtual address and
948c2ecf20Sopenharmony_ci * use it to read/write to PCC registers such as doorbell register
958c2ecf20Sopenharmony_ci *
968c2ecf20Sopenharmony_ci * The below read_register and write_registers are used to read and
978c2ecf20Sopenharmony_ci * write from perf critical registers such as PCC doorbell register
988c2ecf20Sopenharmony_ci */
998c2ecf20Sopenharmony_cistatic int read_register(void __iomem *vaddr, u64 *val, unsigned int bit_width)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	int ret_val = 0;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	switch (bit_width) {
1048c2ecf20Sopenharmony_ci	case 8:
1058c2ecf20Sopenharmony_ci		*val = readb(vaddr);
1068c2ecf20Sopenharmony_ci		break;
1078c2ecf20Sopenharmony_ci	case 16:
1088c2ecf20Sopenharmony_ci		*val = readw(vaddr);
1098c2ecf20Sopenharmony_ci		break;
1108c2ecf20Sopenharmony_ci	case 32:
1118c2ecf20Sopenharmony_ci		*val = readl(vaddr);
1128c2ecf20Sopenharmony_ci		break;
1138c2ecf20Sopenharmony_ci	case 64:
1148c2ecf20Sopenharmony_ci		*val = readq(vaddr);
1158c2ecf20Sopenharmony_ci		break;
1168c2ecf20Sopenharmony_ci	default:
1178c2ecf20Sopenharmony_ci		pr_debug("Error: Cannot read register of %u bit width",
1188c2ecf20Sopenharmony_ci			bit_width);
1198c2ecf20Sopenharmony_ci		ret_val = -EFAULT;
1208c2ecf20Sopenharmony_ci		break;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci	return ret_val;
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic int write_register(void __iomem *vaddr, u64 val, unsigned int bit_width)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	int ret_val = 0;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	switch (bit_width) {
1308c2ecf20Sopenharmony_ci	case 8:
1318c2ecf20Sopenharmony_ci		writeb(val, vaddr);
1328c2ecf20Sopenharmony_ci		break;
1338c2ecf20Sopenharmony_ci	case 16:
1348c2ecf20Sopenharmony_ci		writew(val, vaddr);
1358c2ecf20Sopenharmony_ci		break;
1368c2ecf20Sopenharmony_ci	case 32:
1378c2ecf20Sopenharmony_ci		writel(val, vaddr);
1388c2ecf20Sopenharmony_ci		break;
1398c2ecf20Sopenharmony_ci	case 64:
1408c2ecf20Sopenharmony_ci		writeq(val, vaddr);
1418c2ecf20Sopenharmony_ci		break;
1428c2ecf20Sopenharmony_ci	default:
1438c2ecf20Sopenharmony_ci		pr_debug("Error: Cannot write register of %u bit width",
1448c2ecf20Sopenharmony_ci			bit_width);
1458c2ecf20Sopenharmony_ci		ret_val = -EFAULT;
1468c2ecf20Sopenharmony_ci		break;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci	return ret_val;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci/**
1528c2ecf20Sopenharmony_ci * pcc_map_interrupt - Map a PCC subspace GSI to a linux IRQ number
1538c2ecf20Sopenharmony_ci * @interrupt: GSI number.
1548c2ecf20Sopenharmony_ci * @flags: interrupt flags
1558c2ecf20Sopenharmony_ci *
1568c2ecf20Sopenharmony_ci * Returns: a valid linux IRQ number on success
1578c2ecf20Sopenharmony_ci *		0 or -EINVAL on failure
1588c2ecf20Sopenharmony_ci */
1598c2ecf20Sopenharmony_cistatic int pcc_map_interrupt(u32 interrupt, u32 flags)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	int trigger, polarity;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	if (!interrupt)
1648c2ecf20Sopenharmony_ci		return 0;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	trigger = (flags & ACPI_PCCT_INTERRUPT_MODE) ? ACPI_EDGE_SENSITIVE
1678c2ecf20Sopenharmony_ci			: ACPI_LEVEL_SENSITIVE;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	polarity = (flags & ACPI_PCCT_INTERRUPT_POLARITY) ? ACPI_ACTIVE_LOW
1708c2ecf20Sopenharmony_ci			: ACPI_ACTIVE_HIGH;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	return acpi_register_gsi(NULL, interrupt, trigger, polarity);
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci/**
1768c2ecf20Sopenharmony_ci * pcc_mbox_irq - PCC mailbox interrupt handler
1778c2ecf20Sopenharmony_ci */
1788c2ecf20Sopenharmony_cistatic irqreturn_t pcc_mbox_irq(int irq, void *p)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	struct acpi_generic_address *doorbell_ack;
1818c2ecf20Sopenharmony_ci	struct acpi_pcct_hw_reduced *pcct_ss;
1828c2ecf20Sopenharmony_ci	struct mbox_chan *chan = p;
1838c2ecf20Sopenharmony_ci	u64 doorbell_ack_preserve;
1848c2ecf20Sopenharmony_ci	u64 doorbell_ack_write;
1858c2ecf20Sopenharmony_ci	u64 doorbell_ack_val;
1868c2ecf20Sopenharmony_ci	int ret;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	pcct_ss = chan->con_priv;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	mbox_chan_received_data(chan, NULL);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	if (pcct_ss->header.type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2) {
1938c2ecf20Sopenharmony_ci		struct acpi_pcct_hw_reduced_type2 *pcct2_ss = chan->con_priv;
1948c2ecf20Sopenharmony_ci		u32 id = chan - pcc_mbox_channels;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci		doorbell_ack = &pcct2_ss->platform_ack_register;
1978c2ecf20Sopenharmony_ci		doorbell_ack_preserve = pcct2_ss->ack_preserve_mask;
1988c2ecf20Sopenharmony_ci		doorbell_ack_write = pcct2_ss->ack_write_mask;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci		ret = read_register(pcc_doorbell_ack_vaddr[id],
2018c2ecf20Sopenharmony_ci				    &doorbell_ack_val,
2028c2ecf20Sopenharmony_ci				    doorbell_ack->bit_width);
2038c2ecf20Sopenharmony_ci		if (ret)
2048c2ecf20Sopenharmony_ci			return IRQ_NONE;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci		ret = write_register(pcc_doorbell_ack_vaddr[id],
2078c2ecf20Sopenharmony_ci				     (doorbell_ack_val & doorbell_ack_preserve)
2088c2ecf20Sopenharmony_ci					| doorbell_ack_write,
2098c2ecf20Sopenharmony_ci				     doorbell_ack->bit_width);
2108c2ecf20Sopenharmony_ci		if (ret)
2118c2ecf20Sopenharmony_ci			return IRQ_NONE;
2128c2ecf20Sopenharmony_ci	}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2158c2ecf20Sopenharmony_ci}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci/**
2188c2ecf20Sopenharmony_ci * pcc_mbox_request_channel - PCC clients call this function to
2198c2ecf20Sopenharmony_ci *		request a pointer to their PCC subspace, from which they
2208c2ecf20Sopenharmony_ci *		can get the details of communicating with the remote.
2218c2ecf20Sopenharmony_ci * @cl: Pointer to Mailbox client, so we know where to bind the
2228c2ecf20Sopenharmony_ci *		Channel.
2238c2ecf20Sopenharmony_ci * @subspace_id: The PCC Subspace index as parsed in the PCC client
2248c2ecf20Sopenharmony_ci *		ACPI package. This is used to lookup the array of PCC
2258c2ecf20Sopenharmony_ci *		subspaces as parsed by the PCC Mailbox controller.
2268c2ecf20Sopenharmony_ci *
2278c2ecf20Sopenharmony_ci * Return: Pointer to the Mailbox Channel if successful or
2288c2ecf20Sopenharmony_ci *		ERR_PTR.
2298c2ecf20Sopenharmony_ci */
2308c2ecf20Sopenharmony_cistruct mbox_chan *pcc_mbox_request_channel(struct mbox_client *cl,
2318c2ecf20Sopenharmony_ci		int subspace_id)
2328c2ecf20Sopenharmony_ci{
2338c2ecf20Sopenharmony_ci	struct device *dev = pcc_mbox_ctrl.dev;
2348c2ecf20Sopenharmony_ci	struct mbox_chan *chan;
2358c2ecf20Sopenharmony_ci	unsigned long flags;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	/*
2388c2ecf20Sopenharmony_ci	 * Each PCC Subspace is a Mailbox Channel.
2398c2ecf20Sopenharmony_ci	 * The PCC Clients get their PCC Subspace ID
2408c2ecf20Sopenharmony_ci	 * from their own tables and pass it here.
2418c2ecf20Sopenharmony_ci	 * This returns a pointer to the PCC subspace
2428c2ecf20Sopenharmony_ci	 * for the Client to operate on.
2438c2ecf20Sopenharmony_ci	 */
2448c2ecf20Sopenharmony_ci	chan = get_pcc_channel(subspace_id);
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	if (IS_ERR(chan) || chan->cl) {
2478c2ecf20Sopenharmony_ci		dev_err(dev, "Channel not found for idx: %d\n", subspace_id);
2488c2ecf20Sopenharmony_ci		return ERR_PTR(-EBUSY);
2498c2ecf20Sopenharmony_ci	}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	spin_lock_irqsave(&chan->lock, flags);
2528c2ecf20Sopenharmony_ci	chan->msg_free = 0;
2538c2ecf20Sopenharmony_ci	chan->msg_count = 0;
2548c2ecf20Sopenharmony_ci	chan->active_req = NULL;
2558c2ecf20Sopenharmony_ci	chan->cl = cl;
2568c2ecf20Sopenharmony_ci	init_completion(&chan->tx_complete);
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	if (chan->txdone_method == TXDONE_BY_POLL && cl->knows_txdone)
2598c2ecf20Sopenharmony_ci		chan->txdone_method = TXDONE_BY_ACK;
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&chan->lock, flags);
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	if (pcc_doorbell_irq[subspace_id] > 0) {
2648c2ecf20Sopenharmony_ci		int rc;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci		rc = devm_request_irq(dev, pcc_doorbell_irq[subspace_id],
2678c2ecf20Sopenharmony_ci				      pcc_mbox_irq, 0, MBOX_IRQ_NAME, chan);
2688c2ecf20Sopenharmony_ci		if (unlikely(rc)) {
2698c2ecf20Sopenharmony_ci			dev_err(dev, "failed to register PCC interrupt %d\n",
2708c2ecf20Sopenharmony_ci				pcc_doorbell_irq[subspace_id]);
2718c2ecf20Sopenharmony_ci			pcc_mbox_free_channel(chan);
2728c2ecf20Sopenharmony_ci			chan = ERR_PTR(rc);
2738c2ecf20Sopenharmony_ci		}
2748c2ecf20Sopenharmony_ci	}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	return chan;
2778c2ecf20Sopenharmony_ci}
2788c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcc_mbox_request_channel);
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci/**
2818c2ecf20Sopenharmony_ci * pcc_mbox_free_channel - Clients call this to free their Channel.
2828c2ecf20Sopenharmony_ci *
2838c2ecf20Sopenharmony_ci * @chan: Pointer to the mailbox channel as returned by
2848c2ecf20Sopenharmony_ci *		pcc_mbox_request_channel()
2858c2ecf20Sopenharmony_ci */
2868c2ecf20Sopenharmony_civoid pcc_mbox_free_channel(struct mbox_chan *chan)
2878c2ecf20Sopenharmony_ci{
2888c2ecf20Sopenharmony_ci	u32 id = chan - pcc_mbox_channels;
2898c2ecf20Sopenharmony_ci	unsigned long flags;
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	if (!chan || !chan->cl)
2928c2ecf20Sopenharmony_ci		return;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	if (id >= pcc_mbox_ctrl.num_chans) {
2958c2ecf20Sopenharmony_ci		pr_debug("pcc_mbox_free_channel: Invalid mbox_chan passed\n");
2968c2ecf20Sopenharmony_ci		return;
2978c2ecf20Sopenharmony_ci	}
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	if (pcc_doorbell_irq[id] > 0)
3008c2ecf20Sopenharmony_ci		devm_free_irq(chan->mbox->dev, pcc_doorbell_irq[id], chan);
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	spin_lock_irqsave(&chan->lock, flags);
3038c2ecf20Sopenharmony_ci	chan->cl = NULL;
3048c2ecf20Sopenharmony_ci	chan->active_req = NULL;
3058c2ecf20Sopenharmony_ci	if (chan->txdone_method == TXDONE_BY_ACK)
3068c2ecf20Sopenharmony_ci		chan->txdone_method = TXDONE_BY_POLL;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&chan->lock, flags);
3098c2ecf20Sopenharmony_ci}
3108c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcc_mbox_free_channel);
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci/**
3138c2ecf20Sopenharmony_ci * pcc_send_data - Called from Mailbox Controller code. Used
3148c2ecf20Sopenharmony_ci *		here only to ring the channel doorbell. The PCC client
3158c2ecf20Sopenharmony_ci *		specific read/write is done in the client driver in
3168c2ecf20Sopenharmony_ci *		order to maintain atomicity over PCC channel once
3178c2ecf20Sopenharmony_ci *		OS has control over it. See above for flow of operations.
3188c2ecf20Sopenharmony_ci * @chan: Pointer to Mailbox channel over which to send data.
3198c2ecf20Sopenharmony_ci * @data: Client specific data written over channel. Used here
3208c2ecf20Sopenharmony_ci *		only for debug after PCC transaction completes.
3218c2ecf20Sopenharmony_ci *
3228c2ecf20Sopenharmony_ci * Return: Err if something failed else 0 for success.
3238c2ecf20Sopenharmony_ci */
3248c2ecf20Sopenharmony_cistatic int pcc_send_data(struct mbox_chan *chan, void *data)
3258c2ecf20Sopenharmony_ci{
3268c2ecf20Sopenharmony_ci	struct acpi_pcct_hw_reduced *pcct_ss = chan->con_priv;
3278c2ecf20Sopenharmony_ci	struct acpi_generic_address *doorbell;
3288c2ecf20Sopenharmony_ci	u64 doorbell_preserve;
3298c2ecf20Sopenharmony_ci	u64 doorbell_val;
3308c2ecf20Sopenharmony_ci	u64 doorbell_write;
3318c2ecf20Sopenharmony_ci	u32 id = chan - pcc_mbox_channels;
3328c2ecf20Sopenharmony_ci	int ret = 0;
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	if (id >= pcc_mbox_ctrl.num_chans) {
3358c2ecf20Sopenharmony_ci		pr_debug("pcc_send_data: Invalid mbox_chan passed\n");
3368c2ecf20Sopenharmony_ci		return -ENOENT;
3378c2ecf20Sopenharmony_ci	}
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	doorbell = &pcct_ss->doorbell_register;
3408c2ecf20Sopenharmony_ci	doorbell_preserve = pcct_ss->preserve_mask;
3418c2ecf20Sopenharmony_ci	doorbell_write = pcct_ss->write_mask;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	/* Sync notification from OS to Platform. */
3448c2ecf20Sopenharmony_ci	if (pcc_doorbell_vaddr[id]) {
3458c2ecf20Sopenharmony_ci		ret = read_register(pcc_doorbell_vaddr[id], &doorbell_val,
3468c2ecf20Sopenharmony_ci			doorbell->bit_width);
3478c2ecf20Sopenharmony_ci		if (ret)
3488c2ecf20Sopenharmony_ci			return ret;
3498c2ecf20Sopenharmony_ci		ret = write_register(pcc_doorbell_vaddr[id],
3508c2ecf20Sopenharmony_ci			(doorbell_val & doorbell_preserve) | doorbell_write,
3518c2ecf20Sopenharmony_ci			doorbell->bit_width);
3528c2ecf20Sopenharmony_ci	} else {
3538c2ecf20Sopenharmony_ci		ret = acpi_read(&doorbell_val, doorbell);
3548c2ecf20Sopenharmony_ci		if (ret)
3558c2ecf20Sopenharmony_ci			return ret;
3568c2ecf20Sopenharmony_ci		ret = acpi_write((doorbell_val & doorbell_preserve) | doorbell_write,
3578c2ecf20Sopenharmony_ci			doorbell);
3588c2ecf20Sopenharmony_ci	}
3598c2ecf20Sopenharmony_ci	return ret;
3608c2ecf20Sopenharmony_ci}
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_cistatic const struct mbox_chan_ops pcc_chan_ops = {
3638c2ecf20Sopenharmony_ci	.send_data = pcc_send_data,
3648c2ecf20Sopenharmony_ci};
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci/**
3678c2ecf20Sopenharmony_ci * parse_pcc_subspaces -- Count PCC subspaces defined
3688c2ecf20Sopenharmony_ci * @header: Pointer to the ACPI subtable header under the PCCT.
3698c2ecf20Sopenharmony_ci * @end: End of subtable entry.
3708c2ecf20Sopenharmony_ci *
3718c2ecf20Sopenharmony_ci * Return: If we find a PCC subspace entry of a valid type, return 0.
3728c2ecf20Sopenharmony_ci *	Otherwise, return -EINVAL.
3738c2ecf20Sopenharmony_ci *
3748c2ecf20Sopenharmony_ci * This gets called for each entry in the PCC table.
3758c2ecf20Sopenharmony_ci */
3768c2ecf20Sopenharmony_cistatic int parse_pcc_subspace(union acpi_subtable_headers *header,
3778c2ecf20Sopenharmony_ci		const unsigned long end)
3788c2ecf20Sopenharmony_ci{
3798c2ecf20Sopenharmony_ci	struct acpi_pcct_subspace *ss = (struct acpi_pcct_subspace *) header;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	if (ss->header.type < ACPI_PCCT_TYPE_RESERVED)
3828c2ecf20Sopenharmony_ci		return 0;
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci	return -EINVAL;
3858c2ecf20Sopenharmony_ci}
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci/**
3888c2ecf20Sopenharmony_ci * pcc_parse_subspace_irq - Parse the PCC IRQ and PCC ACK register
3898c2ecf20Sopenharmony_ci *		There should be one entry per PCC client.
3908c2ecf20Sopenharmony_ci * @id: PCC subspace index.
3918c2ecf20Sopenharmony_ci * @pcct_ss: Pointer to the ACPI subtable header under the PCCT.
3928c2ecf20Sopenharmony_ci *
3938c2ecf20Sopenharmony_ci * Return: 0 for Success, else errno.
3948c2ecf20Sopenharmony_ci *
3958c2ecf20Sopenharmony_ci * This gets called for each entry in the PCC table.
3968c2ecf20Sopenharmony_ci */
3978c2ecf20Sopenharmony_cistatic int pcc_parse_subspace_irq(int id,
3988c2ecf20Sopenharmony_ci				  struct acpi_pcct_hw_reduced *pcct_ss)
3998c2ecf20Sopenharmony_ci{
4008c2ecf20Sopenharmony_ci	pcc_doorbell_irq[id] = pcc_map_interrupt(pcct_ss->platform_interrupt,
4018c2ecf20Sopenharmony_ci						 (u32)pcct_ss->flags);
4028c2ecf20Sopenharmony_ci	if (pcc_doorbell_irq[id] <= 0) {
4038c2ecf20Sopenharmony_ci		pr_err("PCC GSI %d not registered\n",
4048c2ecf20Sopenharmony_ci		       pcct_ss->platform_interrupt);
4058c2ecf20Sopenharmony_ci		return -EINVAL;
4068c2ecf20Sopenharmony_ci	}
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	if (pcct_ss->header.type
4098c2ecf20Sopenharmony_ci		== ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2) {
4108c2ecf20Sopenharmony_ci		struct acpi_pcct_hw_reduced_type2 *pcct2_ss = (void *)pcct_ss;
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci		pcc_doorbell_ack_vaddr[id] = acpi_os_ioremap(
4138c2ecf20Sopenharmony_ci				pcct2_ss->platform_ack_register.address,
4148c2ecf20Sopenharmony_ci				pcct2_ss->platform_ack_register.bit_width / 8);
4158c2ecf20Sopenharmony_ci		if (!pcc_doorbell_ack_vaddr[id]) {
4168c2ecf20Sopenharmony_ci			pr_err("Failed to ioremap PCC ACK register\n");
4178c2ecf20Sopenharmony_ci			return -ENOMEM;
4188c2ecf20Sopenharmony_ci		}
4198c2ecf20Sopenharmony_ci	}
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	return 0;
4228c2ecf20Sopenharmony_ci}
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci/**
4258c2ecf20Sopenharmony_ci * acpi_pcc_probe - Parse the ACPI tree for the PCCT.
4268c2ecf20Sopenharmony_ci *
4278c2ecf20Sopenharmony_ci * Return: 0 for Success, else errno.
4288c2ecf20Sopenharmony_ci */
4298c2ecf20Sopenharmony_cistatic int __init acpi_pcc_probe(void)
4308c2ecf20Sopenharmony_ci{
4318c2ecf20Sopenharmony_ci	struct acpi_table_header *pcct_tbl;
4328c2ecf20Sopenharmony_ci	struct acpi_subtable_header *pcct_entry;
4338c2ecf20Sopenharmony_ci	struct acpi_table_pcct *acpi_pcct_tbl;
4348c2ecf20Sopenharmony_ci	struct acpi_subtable_proc proc[ACPI_PCCT_TYPE_RESERVED];
4358c2ecf20Sopenharmony_ci	int count, i, rc;
4368c2ecf20Sopenharmony_ci	acpi_status status = AE_OK;
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	/* Search for PCCT */
4398c2ecf20Sopenharmony_ci	status = acpi_get_table(ACPI_SIG_PCCT, 0, &pcct_tbl);
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	if (ACPI_FAILURE(status) || !pcct_tbl)
4428c2ecf20Sopenharmony_ci		return -ENODEV;
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	/* Set up the subtable handlers */
4458c2ecf20Sopenharmony_ci	for (i = ACPI_PCCT_TYPE_GENERIC_SUBSPACE;
4468c2ecf20Sopenharmony_ci	     i < ACPI_PCCT_TYPE_RESERVED; i++) {
4478c2ecf20Sopenharmony_ci		proc[i].id = i;
4488c2ecf20Sopenharmony_ci		proc[i].count = 0;
4498c2ecf20Sopenharmony_ci		proc[i].handler = parse_pcc_subspace;
4508c2ecf20Sopenharmony_ci	}
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	count = acpi_table_parse_entries_array(ACPI_SIG_PCCT,
4538c2ecf20Sopenharmony_ci			sizeof(struct acpi_table_pcct), proc,
4548c2ecf20Sopenharmony_ci			ACPI_PCCT_TYPE_RESERVED, MAX_PCC_SUBSPACES);
4558c2ecf20Sopenharmony_ci	if (count <= 0 || count > MAX_PCC_SUBSPACES) {
4568c2ecf20Sopenharmony_ci		if (count < 0)
4578c2ecf20Sopenharmony_ci			pr_warn("Error parsing PCC subspaces from PCCT\n");
4588c2ecf20Sopenharmony_ci		else
4598c2ecf20Sopenharmony_ci			pr_warn("Invalid PCCT: %d PCC subspaces\n", count);
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci		rc = -EINVAL;
4628c2ecf20Sopenharmony_ci		goto err_put_pcct;
4638c2ecf20Sopenharmony_ci	}
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ci	pcc_mbox_channels = kcalloc(count, sizeof(struct mbox_chan),
4668c2ecf20Sopenharmony_ci				    GFP_KERNEL);
4678c2ecf20Sopenharmony_ci	if (!pcc_mbox_channels) {
4688c2ecf20Sopenharmony_ci		pr_err("Could not allocate space for PCC mbox channels\n");
4698c2ecf20Sopenharmony_ci		rc = -ENOMEM;
4708c2ecf20Sopenharmony_ci		goto err_put_pcct;
4718c2ecf20Sopenharmony_ci	}
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	pcc_doorbell_vaddr = kcalloc(count, sizeof(void *), GFP_KERNEL);
4748c2ecf20Sopenharmony_ci	if (!pcc_doorbell_vaddr) {
4758c2ecf20Sopenharmony_ci		rc = -ENOMEM;
4768c2ecf20Sopenharmony_ci		goto err_free_mbox;
4778c2ecf20Sopenharmony_ci	}
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	pcc_doorbell_ack_vaddr = kcalloc(count, sizeof(void *), GFP_KERNEL);
4808c2ecf20Sopenharmony_ci	if (!pcc_doorbell_ack_vaddr) {
4818c2ecf20Sopenharmony_ci		rc = -ENOMEM;
4828c2ecf20Sopenharmony_ci		goto err_free_db_vaddr;
4838c2ecf20Sopenharmony_ci	}
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	pcc_doorbell_irq = kcalloc(count, sizeof(int), GFP_KERNEL);
4868c2ecf20Sopenharmony_ci	if (!pcc_doorbell_irq) {
4878c2ecf20Sopenharmony_ci		rc = -ENOMEM;
4888c2ecf20Sopenharmony_ci		goto err_free_db_ack_vaddr;
4898c2ecf20Sopenharmony_ci	}
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ci	/* Point to the first PCC subspace entry */
4928c2ecf20Sopenharmony_ci	pcct_entry = (struct acpi_subtable_header *) (
4938c2ecf20Sopenharmony_ci		(unsigned long) pcct_tbl + sizeof(struct acpi_table_pcct));
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	acpi_pcct_tbl = (struct acpi_table_pcct *) pcct_tbl;
4968c2ecf20Sopenharmony_ci	if (acpi_pcct_tbl->flags & ACPI_PCCT_DOORBELL)
4978c2ecf20Sopenharmony_ci		pcc_mbox_ctrl.txdone_irq = true;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci	for (i = 0; i < count; i++) {
5008c2ecf20Sopenharmony_ci		struct acpi_generic_address *db_reg;
5018c2ecf20Sopenharmony_ci		struct acpi_pcct_subspace *pcct_ss;
5028c2ecf20Sopenharmony_ci		pcc_mbox_channels[i].con_priv = pcct_entry;
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci		if (pcct_entry->type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE ||
5058c2ecf20Sopenharmony_ci		    pcct_entry->type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2) {
5068c2ecf20Sopenharmony_ci			struct acpi_pcct_hw_reduced *pcct_hrss;
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci			pcct_hrss = (struct acpi_pcct_hw_reduced *) pcct_entry;
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci			if (pcc_mbox_ctrl.txdone_irq) {
5118c2ecf20Sopenharmony_ci				rc = pcc_parse_subspace_irq(i, pcct_hrss);
5128c2ecf20Sopenharmony_ci				if (rc < 0)
5138c2ecf20Sopenharmony_ci					goto err;
5148c2ecf20Sopenharmony_ci			}
5158c2ecf20Sopenharmony_ci		}
5168c2ecf20Sopenharmony_ci		pcct_ss = (struct acpi_pcct_subspace *) pcct_entry;
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci		/* If doorbell is in system memory cache the virt address */
5198c2ecf20Sopenharmony_ci		db_reg = &pcct_ss->doorbell_register;
5208c2ecf20Sopenharmony_ci		if (db_reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
5218c2ecf20Sopenharmony_ci			pcc_doorbell_vaddr[i] = acpi_os_ioremap(db_reg->address,
5228c2ecf20Sopenharmony_ci							db_reg->bit_width/8);
5238c2ecf20Sopenharmony_ci		pcct_entry = (struct acpi_subtable_header *)
5248c2ecf20Sopenharmony_ci			((unsigned long) pcct_entry + pcct_entry->length);
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ci	pcc_mbox_ctrl.num_chans = count;
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci	pr_info("Detected %d PCC Subspaces\n", pcc_mbox_ctrl.num_chans);
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	return 0;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_cierr:
5348c2ecf20Sopenharmony_ci	kfree(pcc_doorbell_irq);
5358c2ecf20Sopenharmony_cierr_free_db_ack_vaddr:
5368c2ecf20Sopenharmony_ci	kfree(pcc_doorbell_ack_vaddr);
5378c2ecf20Sopenharmony_cierr_free_db_vaddr:
5388c2ecf20Sopenharmony_ci	kfree(pcc_doorbell_vaddr);
5398c2ecf20Sopenharmony_cierr_free_mbox:
5408c2ecf20Sopenharmony_ci	kfree(pcc_mbox_channels);
5418c2ecf20Sopenharmony_cierr_put_pcct:
5428c2ecf20Sopenharmony_ci	acpi_put_table(pcct_tbl);
5438c2ecf20Sopenharmony_ci	return rc;
5448c2ecf20Sopenharmony_ci}
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci/**
5478c2ecf20Sopenharmony_ci * pcc_mbox_probe - Called when we find a match for the
5488c2ecf20Sopenharmony_ci *	PCCT platform device. This is purely used to represent
5498c2ecf20Sopenharmony_ci *	the PCCT as a virtual device for registering with the
5508c2ecf20Sopenharmony_ci *	generic Mailbox framework.
5518c2ecf20Sopenharmony_ci *
5528c2ecf20Sopenharmony_ci * @pdev: Pointer to platform device returned when a match
5538c2ecf20Sopenharmony_ci *	is found.
5548c2ecf20Sopenharmony_ci *
5558c2ecf20Sopenharmony_ci *	Return: 0 for Success, else errno.
5568c2ecf20Sopenharmony_ci */
5578c2ecf20Sopenharmony_cistatic int pcc_mbox_probe(struct platform_device *pdev)
5588c2ecf20Sopenharmony_ci{
5598c2ecf20Sopenharmony_ci	int ret = 0;
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_ci	pcc_mbox_ctrl.chans = pcc_mbox_channels;
5628c2ecf20Sopenharmony_ci	pcc_mbox_ctrl.ops = &pcc_chan_ops;
5638c2ecf20Sopenharmony_ci	pcc_mbox_ctrl.dev = &pdev->dev;
5648c2ecf20Sopenharmony_ci
5658c2ecf20Sopenharmony_ci	pr_info("Registering PCC driver as Mailbox controller\n");
5668c2ecf20Sopenharmony_ci	ret = mbox_controller_register(&pcc_mbox_ctrl);
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ci	if (ret) {
5698c2ecf20Sopenharmony_ci		pr_err("Err registering PCC as Mailbox controller: %d\n", ret);
5708c2ecf20Sopenharmony_ci		ret = -ENODEV;
5718c2ecf20Sopenharmony_ci	}
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_ci	return ret;
5748c2ecf20Sopenharmony_ci}
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_cistatic struct platform_driver pcc_mbox_driver = {
5778c2ecf20Sopenharmony_ci	.probe = pcc_mbox_probe,
5788c2ecf20Sopenharmony_ci	.driver = {
5798c2ecf20Sopenharmony_ci		.name = "PCCT",
5808c2ecf20Sopenharmony_ci		.owner = THIS_MODULE,
5818c2ecf20Sopenharmony_ci	},
5828c2ecf20Sopenharmony_ci};
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_cistatic int __init pcc_init(void)
5858c2ecf20Sopenharmony_ci{
5868c2ecf20Sopenharmony_ci	int ret;
5878c2ecf20Sopenharmony_ci	struct platform_device *pcc_pdev;
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci	if (acpi_disabled)
5908c2ecf20Sopenharmony_ci		return -ENODEV;
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ci	/* Check if PCC support is available. */
5938c2ecf20Sopenharmony_ci	ret = acpi_pcc_probe();
5948c2ecf20Sopenharmony_ci
5958c2ecf20Sopenharmony_ci	if (ret) {
5968c2ecf20Sopenharmony_ci		pr_debug("ACPI PCC probe failed.\n");
5978c2ecf20Sopenharmony_ci		return -ENODEV;
5988c2ecf20Sopenharmony_ci	}
5998c2ecf20Sopenharmony_ci
6008c2ecf20Sopenharmony_ci	pcc_pdev = platform_create_bundle(&pcc_mbox_driver,
6018c2ecf20Sopenharmony_ci			pcc_mbox_probe, NULL, 0, NULL, 0);
6028c2ecf20Sopenharmony_ci
6038c2ecf20Sopenharmony_ci	if (IS_ERR(pcc_pdev)) {
6048c2ecf20Sopenharmony_ci		pr_debug("Err creating PCC platform bundle\n");
6058c2ecf20Sopenharmony_ci		return PTR_ERR(pcc_pdev);
6068c2ecf20Sopenharmony_ci	}
6078c2ecf20Sopenharmony_ci
6088c2ecf20Sopenharmony_ci	return 0;
6098c2ecf20Sopenharmony_ci}
6108c2ecf20Sopenharmony_ci
6118c2ecf20Sopenharmony_ci/*
6128c2ecf20Sopenharmony_ci * Make PCC init postcore so that users of this mailbox
6138c2ecf20Sopenharmony_ci * such as the ACPI Processor driver have it available
6148c2ecf20Sopenharmony_ci * at their init.
6158c2ecf20Sopenharmony_ci */
6168c2ecf20Sopenharmony_cipostcore_initcall(pcc_init);
617