/*
* CPPC (Collaborative Processor Performance Control) methods used by CPUfreq drivers.
*
* (C) Copyright 2014, 2015 Linaro Ltd.
* Author: Ashwin Chaugule <ashwin.chaugule@linaro.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*
* CPPC describes a few methods for controlling CPU performance using
* information from a per CPU table called CPC. This table is described in
* the ACPI v5.0+ specification. The table consists of a list of
* registers which may be memory mapped or hardware registers and also may
* include some static integer values.
*
* CPU performance is on an abstract continuous scale as against a discretized
* P-state scale which is tied to CPU frequency only. In brief, the basic
* operation involves:
*
* - OS makes a CPU performance request. (Can provide min and max bounds)
*
* - Platform (such as BMC) is free to optimize request within requested bounds
* depending on power/thermal budgets etc.
*
* - Platform conveys its decision back to OS
*
* The communication between OS and platform occurs through another medium
* called (PCC) Platform Communication Channel. This is a generic mailbox like
* mechanism which includes doorbell semantics to indicate register updates.
* See drivers/mailbox/pcc.c for details on PCC.
*
* Finer details about the PCC and CPPC spec are available in the ACPI v5.1 and
* above specifications.
*/
#define pr_fmt(fmt) "ACPI CPPC: " fmt
#include <linux/cpufreq.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/rwsem.h>
#include <linux/wait.h>
#include <acpi/cppc_acpi.h>
struct cppc_pcc_data {
struct mbox_chan *pcc_channel;
void __iomem *pcc_comm_addr;
bool pcc_channel_acquired;
ktime_t deadline;
unsigned int pcc_mpar, pcc_mrtt, pcc_nominal;
bool pending_pcc_write_cmd; /* Any pending/batched PCC write cmds? */
bool platform_owns_pcc; /* Ownership of PCC subspace */
unsigned int pcc_write_cnt; /* Running count of PCC write commands */
/*
* Lock to provide controlled access to the PCC channel.
*
* For performance critical usecases(currently cppc_set_perf)
* We need to take read_lock and check if channel belongs to OSPM
* before reading or writing to PCC subspace
* We need to take write_lock before transferring the channel
* ownership to the platform via a Doorbell
* This allows us to batch a number of CPPC requests if they happen
* to originate in about the same time
*
* For non-performance critical usecases(init)
* Take write_lock for all purposes which gives exclusive access
*/
struct rw_semaphore pcc_lock;
/* Wait queue for CPUs whose requests were batched */
wait_queue_head_t pcc_write_wait_q;
ktime_t last_cmd_cmpl_time;
ktime_t last_mpar_reset;
int mpar_count;
int refcount;
};
/* Array to represent the PCC channel per subspace id */
static struct cppc_pcc_data *pcc_data[MAX_PCC_SUBSPACES];
/* The cpu_pcc_subspace_idx containsper CPU subspace id */
static DEFINE_PER_CPU(int, cpu_pcc_subspace_idx);
/*
* The cpc_desc structure contains the ACPI register details
* as described in the per CPU _CPC tables. The details
* include the type of register (e.g. PCC, System IO, FFH etc.)
* and destination addresses which lets us READ/WRITE CPU performance
* information using the appropriate I/O methods.
*/
static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr);
/* pcc mapped address + header size + offset within PCC subspace */
#define GET_PCC_VADDR(offs, pcc_ss_id) (pcc_data[pcc_ss_id]->pcc_comm_addr + \
0x8 + (offs))
/* Check if a CPC register is in PCC */
#define CPC_IN_PCC(cpc) ((cpc)->type == ACPI_TYPE_BUFFER && \
(cpc)->cpc_entry.reg.space_id == \
ACPI_ADR_SPACE_PLATFORM_COMM)
/* Evalutes to True if reg is a NULL register descriptor */
#define IS_NULL_REG(reg) ((reg)->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY && \
(reg)->address == 0 && \
(reg)->bit_width == 0 && \
(reg)->bit_offset == 0 && \
(reg)->access_width == 0)
/* Evalutes to True if an optional cpc field is supported */
#define CPC_SUPPORTED(cpc) ((cpc)->type == ACPI_TYPE_INTEGER ? \
!!(cpc)->cpc_entry.int_value : \
!IS_NULL_REG(&(cpc)->cpc_entry.reg))
/*
* Arbitrary Retries in case the remote processor is slow to respond
* to PCC commands. Keeping it high enough to cover emulators where
* the processors run painfully slow.
*/
#define NUM_RETRIES 500ULL
struct cppc_attr {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj,
struct attribute *attr, char *buf);
ssize_t (*store)(struct kobject *kobj,
struct attribute *attr, const char *c, ssize_t count);
};