aboutsummaryrefslogtreecommitdiff
path: root/hw/occ.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/occ.c')
-rw-r--r--hw/occ.c477
1 files changed, 477 insertions, 0 deletions
diff --git a/hw/occ.c b/hw/occ.c
new file mode 100644
index 0000000..7e6284a
--- /dev/null
+++ b/hw/occ.c
@@ -0,0 +1,477 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <skiboot.h>
+#include <xscom.h>
+#include <io.h>
+#include <cpu.h>
+#include <chip.h>
+#include <mem_region.h>
+#include <fsp.h>
+#include <timebase.h>
+#include <hostservices.h>
+#include <fsp-elog.h>
+
+/* OCC Communication Area for PStates */
+
+#define P8_HOMER_SAPPHIRE_DATA_OFFSET 0x1F8000
+
+#define MAX_PSTATES 256
+
+struct occ_pstate_entry {
+ s8 id;
+ u8 flags;
+ u8 vdd;
+ u8 vcs;
+ u32 freq_khz;
+};
+
+struct occ_pstate_table {
+ u8 valid;
+ u8 version;
+ u8 throttle;
+ s8 pstate_min;
+ s8 pstate_nom;
+ s8 pstate_max;
+ u8 spare1;
+ u8 spare2;
+ u64 reserved;
+ struct occ_pstate_entry pstates[MAX_PSTATES];
+};
+
+DEFINE_LOG_ENTRY(OPAL_RC_OCC_LOAD, OPAL_PLATFORM_ERR_EVT, OPAL_OCC,
+ OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
+ OPAL_NA, NULL);
+
+DEFINE_LOG_ENTRY(OPAL_RC_OCC_RESET, OPAL_PLATFORM_ERR_EVT, OPAL_OCC,
+ OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL,
+ OPAL_NA, NULL);
+
+DEFINE_LOG_ENTRY(OPAL_RC_OCC_PSTATE_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_OCC,
+ OPAL_CEC_HARDWARE, OPAL_INFO,
+ OPAL_NA, NULL);
+
+/* Check each chip's HOMER/Sapphire area for PState valid bit */
+static bool wait_for_all_occ_init(void)
+{
+ struct proc_chip *chip;
+ uint64_t occ_data_area;
+ struct occ_pstate_table *occ_data;
+ int tries;
+ uint64_t start_time, end_time;
+
+ start_time = mftb();
+ for_each_chip(chip) {
+ /* Check for valid homer address */
+ if (!chip->homer_base) {
+ printf("OCC: Chip: %x homer_base is not valid\n",
+ chip->id);
+ return false;
+ }
+ /* Get PState table address */
+ occ_data_area = chip->homer_base + P8_HOMER_SAPPHIRE_DATA_OFFSET;
+ occ_data = (struct occ_pstate_table *)occ_data_area;
+
+ /*
+ * Checking for occ_data->valid == 1 is ok because we clear all
+ * homer_base+size before passing memory to host services.
+ * This ensures occ_data->valid == 0 before OCC load
+ */
+ tries = 20; /* 2 secs */
+ while((occ_data->valid != 1) && tries--) {
+ time_wait_ms(100);
+ }
+ if (occ_data->valid != 1) {
+ printf("OCC: Chip: %x PState table is not valid\n",
+ chip->id);
+ return false;
+ }
+ printf("OCC: Chip %02x Data (%016llx) = %016llx\n",
+ chip->id, occ_data_area,
+ *(uint64_t *)occ_data_area);
+ }
+ end_time = mftb();
+ printf("OCC: All Chip Rdy after %lld ms\n", (end_time - start_time) / 512 / 1000);
+ return true;
+}
+
+/* Add device tree properties to describe pstates states */
+/* Retrun nominal pstate to set in each core */
+static bool add_cpu_pstate_properties(s8 *pstate_nom)
+{
+ struct proc_chip *chip;
+ uint64_t occ_data_area;
+ struct occ_pstate_table *occ_data;
+ struct dt_node *power_mgt;
+ u8 nr_pstates;
+ /* Arrays for device tree */
+ u32 dt_id[MAX_PSTATES];
+ u32 dt_freq[MAX_PSTATES];
+ int i;
+
+ printf("OCC: CPU pstate state device tree init\n");
+
+ /* Find first chip and core */
+ chip = next_chip(NULL);
+
+ /* Extract PState information from OCC */
+
+ /* Dump state table */
+ occ_data_area = chip->homer_base + P8_HOMER_SAPPHIRE_DATA_OFFSET;
+
+ printf("OCC: Data (%16llx) = %16llx %16llx\n",
+ occ_data_area,
+ *(uint64_t *)occ_data_area,
+ *(uint64_t *)(occ_data_area+8));
+
+ occ_data = (struct occ_pstate_table *)occ_data_area;
+
+ if (!occ_data->valid) {
+ printf("OCC: PState table is not valid\n");
+ return false;
+ }
+
+ nr_pstates = occ_data->pstate_max - occ_data->pstate_min + 1;
+ printf("OCC: Min %d Nom %d Max %d Nr States %d\n",
+ occ_data->pstate_min, occ_data->pstate_nom,
+ occ_data->pstate_max, nr_pstates);
+
+ if (nr_pstates <= 1 || nr_pstates > 128) {
+ printf("OCC: OCC range is not valid\n");
+ return false;
+ }
+
+ /* Setup arrays for device-tree */
+ for( i=0; i < nr_pstates; i++) {
+ dt_id[i] = occ_data->pstates[i].id;
+ dt_freq[i] = occ_data->pstates[i].freq_khz/1000;
+ }
+
+ power_mgt = dt_find_by_path(dt_root, "/ibm,opal/power-mgt");
+ if (!power_mgt) {
+ printf("OCC: dt node /ibm,opal/power-mgt not found\n");
+ return false;
+ }
+
+ /* Add the device-tree entries */
+ dt_add_property(power_mgt, "ibm,pstate-ids", dt_id, nr_pstates * 4);
+ dt_add_property(power_mgt, "ibm,pstate-frequencies-mhz", dt_freq, nr_pstates * 4);
+ dt_add_property_cells(power_mgt, "ibm,pstate-min", occ_data->pstate_min);
+ dt_add_property_cells(power_mgt, "ibm,pstate-nominal", occ_data->pstate_nom);
+ dt_add_property_cells(power_mgt, "ibm,pstate-max", occ_data->pstate_max);
+
+ /* Return pstate to set for each core */
+ *pstate_nom = occ_data->pstate_nom;
+ return true;
+}
+
+/*
+ * Prepare chip for pstate transitions
+ */
+
+static bool cpu_pstates_prepare_core(struct proc_chip *chip, struct cpu_thread *c, s8 pstate_nom)
+{
+ uint32_t core = pir_to_core_id(c->pir);
+ uint64_t tmp, pstate;
+ int rc;
+
+ /*
+ * Currently Fastsleep init clears EX_PM_SPR_OVERRIDE_EN.
+ * Need to ensure only relevant bits are inited
+ */
+
+ /* Init PM GP1 for SCOM based PSTATE control to set nominal freq */
+ rc = xscom_read(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP1), &tmp);
+ tmp = tmp | EX_PM_SETUP_GP1_PM_SPR_OVERRIDE_EN;
+ rc = xscom_write(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP1), tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_PSTATE_INIT),
+ "OCC: Failed to write PM_GP1 in pstates init\n");
+ return false;
+ }
+
+ /* Set new pstate to core */
+ rc = xscom_read(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_PPMCR), &tmp);
+ tmp = tmp & ~0xFFFF000000000000ULL;
+ pstate = ((uint64_t) pstate_nom) & 0xFF;
+ tmp = tmp | (pstate << 56) | (pstate << 48);
+ rc = xscom_write(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_PPMCR), tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_PSTATE_INIT),
+ "OCC: Failed to write PM_GP1 in pstates init\n");
+ return false;
+ }
+ time_wait_ms(1); /* Wait for PState to change */
+ /*
+ * Init PM GP1 for SPR based PSTATE control.
+ * Once OCC is active EX_PM_SETUP_GP1_DPLL_FREQ_OVERRIDE_EN will be
+ * cleared by OCC. Sapphire need not clear.
+ * However wait for DVFS state machine to become idle after min->nominal
+ * transition initiated above. If not switch over to SPR control could fail.
+ */
+ rc = xscom_read(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP1), &tmp);
+ tmp = tmp & ~EX_PM_SETUP_GP1_PM_SPR_OVERRIDE_EN;
+ rc = xscom_write(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP1), tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_PSTATE_INIT),
+ "OCC: Failed to write PM_GP1 in pstates init\n");
+ return false;
+ }
+
+ /* Just debug */
+ rc = xscom_read(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_PPMSR), &tmp);
+ printf("OCC: Chip %x Core %x PPMSR %016llx\n", chip->id, core, tmp);
+
+ /*
+ * If PMSR is still in transition at this point due to PState change
+ * initiated above, then the switchover to SPR may not work.
+ * ToDo: Check for DVFS state machine idle before change.
+ */
+
+ return true;
+}
+
+/* CPU-OCC PState init */
+/* Called after OCC init on P8 */
+void occ_pstates_init(void)
+{
+ struct proc_chip *chip;
+ struct cpu_thread *c;
+ s8 pstate_nom;
+
+ /* OCC is P8 only */
+ if (proc_gen != proc_gen_p8)
+ return;
+
+ chip = next_chip(NULL);
+ if (!chip->homer_base) {
+ log_simple_error(&e_info(OPAL_RC_OCC_PSTATE_INIT),
+ "OCC: No HOMER detected, assuming no pstates\n");
+ return;
+ }
+
+ /* Wait for all OCC to boot up */
+ if(!wait_for_all_occ_init()) {
+ log_simple_error(&e_info(OPAL_RC_OCC_PSTATE_INIT),
+ "OCC: All OCC did not init. Timed Out\n");
+ return;
+ }
+
+ /*
+ * Check boundary conditions and add device tree nodes
+ * and return nominal pstate to set for the core
+ */
+ if (!add_cpu_pstate_properties(&pstate_nom)) {
+ log_simple_error(&e_info(OPAL_RC_OCC_PSTATE_INIT),
+ "Skiping core cpufreq init due to OCC error\n");
+ return;
+ }
+
+ /* Setup host based pstates and set nominal frequency */
+ for_each_chip(chip) {
+ for_each_available_core_in_chip(c, chip->id) {
+ cpu_pstates_prepare_core(chip, c, pstate_nom);
+ }
+ }
+}
+
+static void occ_do_load(u8 scope, u32 dbob_id __unused, u32 seq_id)
+{
+ struct fsp_msg *rsp, *stat;
+ int rc = -ENOMEM;
+ int status_word = 0;
+ struct proc_chip *chip = next_chip(NULL);
+ u8 err = 0;
+
+ /* Check arguments */
+ if (scope != 0x01 && scope != 0x02) {
+ prerror("OCC: Load message with invalid scope 0x%x\n",
+ scope);
+ err = 0x22;
+ }
+
+ /* First queue up an OK response to the load message itself */
+ rsp = fsp_mkmsg(FSP_RSP_LOAD_OCC, 0 | err);
+ if (rsp)
+ rc = fsp_queue_msg(rsp, fsp_freemsg);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
+ "OCC: Error %d queueing FSP OCC LOAD reply\n", rc);
+ return;
+ }
+
+ /* If we had an error, return */
+ if (err)
+ return;
+
+ /* Call HBRT... */
+ rc = host_services_occ_load();
+
+ /* Handle fallback to preload */
+ if (rc == -ENOENT && chip->homer_base) {
+ printf("OCC: Load: Fallback to preloaded image\n");
+ rc = 0;
+ } else if (!rc) {
+ /* Success, start OCC */
+ rc = host_services_occ_start();
+ }
+ if (rc) {
+ /* If either of hostservices call fail, send fail to FSP */
+ /* Find a chip ID to send failure */
+ for_each_chip(chip) {
+ if (scope == 0x01 && dbob_id != chip->dbob_id)
+ continue;
+ status_word = 0xB500 | (chip->pcid & 0xff);
+ break;
+ }
+ log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
+ "OCC: Error %d in load/start OCC\n", err);
+ }
+
+ /* Send a single response for all chips */
+ stat = fsp_mkmsg(FSP_CMD_LOAD_OCC_STAT, 2, status_word, seq_id);
+ if (stat)
+ rc = fsp_queue_msg(stat, fsp_freemsg);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_LOAD),
+ "OCC: Error %d queueing FSP OCC LOAD STATUS msg", rc);
+ }
+}
+
+static void occ_do_reset(u8 scope, u32 dbob_id, u32 seq_id)
+{
+ struct fsp_msg *rsp, *stat;
+ struct proc_chip *chip = next_chip(NULL);
+ int rc = -ENOMEM;
+ u8 err = 0;
+
+ /* Check arguments */
+ if (scope != 0x01 && scope != 0x02) {
+ prerror("OCC: Reset message with invalid scope 0x%x\n",
+ scope);
+ err = 0x22;
+ }
+
+ /* First queue up an OK response to the reset message itself */
+ rsp = fsp_mkmsg(FSP_RSP_RESET_OCC, 0 | err);
+ if (rsp)
+ rc = fsp_queue_msg(rsp, fsp_freemsg);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_RESET),
+ "OCC: Error %d queueing FSP OCC RESET reply\n", rc);
+ return;
+ }
+
+ /* If we had an error, return */
+ if (err)
+ return;
+
+ /* Call HBRT... */
+ rc = host_services_occ_start();
+
+ /* Handle fallback to preload */
+ if (rc == -ENOENT && chip->homer_base) {
+ printf("OCC: Reset: Fallback to preloaded image\n");
+ rc = 0;
+ }
+ if (!rc) {
+ /* Send a single success response for all chips */
+ stat = fsp_mkmsg(FSP_CMD_RESET_OCC_STAT, 2, 0, seq_id);
+ if (stat)
+ rc = fsp_queue_msg(stat, fsp_freemsg);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_RESET),
+ "OCC: Error %d queueing FSP OCC RESET"
+ " STATUS message\n", rc);
+ }
+ } else {
+
+ /*
+ * Then send a matching OCC Reset Status message with an 0xFE
+ * (fail) response code as well to the first matching chip
+ */
+ for_each_chip(chip) {
+ if (scope == 0x01 && dbob_id != chip->dbob_id)
+ continue;
+ rc = -ENOMEM;
+ stat = fsp_mkmsg(FSP_CMD_RESET_OCC_STAT, 2,
+ 0xfe00 | (chip->pcid & 0xff), seq_id);
+ if (stat)
+ rc = fsp_queue_msg(stat, fsp_freemsg);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_OCC_RESET),
+ "OCC: Error %d queueing FSP OCC RESET"
+ " STATUS message\n", rc);
+ }
+ break;
+ }
+ }
+}
+
+static bool fsp_occ_msg(u32 cmd_sub_mod, struct fsp_msg *msg)
+{
+ u32 dbob_id, seq_id;
+ u8 scope;
+
+ switch (cmd_sub_mod) {
+ case FSP_CMD_LOAD_OCC:
+ /*
+ * We get the "Load OCC" command at boot. We don't currently
+ * support loading it ourselves (we don't have the procedures,
+ * they will come with Host Services). For now HostBoot will
+ * have loaded a OCC firmware for us, but we still need to
+ * be nice and respond to OCC.
+ */
+ scope = msg->data.bytes[3];
+ dbob_id = msg->data.words[1];
+ seq_id = msg->data.words[2];
+ printf("OCC: Got OCC Load message, scope=0x%x dbob=0x%x"
+ " seq=0x%x\n", scope, dbob_id, seq_id);
+ occ_do_load(scope, dbob_id, seq_id);
+ return true;
+
+ case FSP_CMD_RESET_OCC:
+ /*
+ * We shouldn't be getting this one, but if we do, we have
+ * to reply something sensible or the FSP will get upset
+ */
+ scope = msg->data.bytes[3];
+ dbob_id = msg->data.words[1];
+ seq_id = msg->data.words[2];
+ printf("OCC: Got OCC Reset message, scope=0x%x dbob=0x%x"
+ " seq=0x%x\n", scope, dbob_id, seq_id);
+ occ_do_reset(scope, dbob_id, seq_id);
+ return true;
+ }
+ return false;
+}
+
+static struct fsp_client fsp_occ_client = {
+ .message = fsp_occ_msg,
+};
+
+void occ_fsp_init(void)
+{
+ /* OCC is P8 only */
+ if (proc_gen != proc_gen_p8)
+ return;
+
+ /* If we have an FSP, register for notifications */
+ if (fsp_present())
+ fsp_register_client(&fsp_occ_client, FSP_MCLASS_OCC);
+}
+
+