summaryrefslogtreecommitdiffstats
path: root/hw/npu-hw-procedures.c
diff options
context:
space:
mode:
authorAlistair Popple <alistair@popple.id.au>2015-10-16 16:08:19 +1100
committerStewart Smith <stewart@linux.vnet.ibm.com>2015-10-26 14:12:31 +1100
commitcc617995bda60a3817b0bc2796fef169bd895aba (patch)
treedff6f4a2224007a47b2fb6842cddd12c2442223d /hw/npu-hw-procedures.c
parent4b4a8ca3dddc234f82c55dc2f1062f19b777f8d2 (diff)
downloadblackbird-skiboot-cc617995bda60a3817b0bc2796fef169bd895aba.tar.gz
blackbird-skiboot-cc617995bda60a3817b0bc2796fef169bd895aba.zip
Nvlink: Add NPU PHB functions
This patch adds support for the NPU Nvlink PHB type. It provides access to each nvlink in the system by exposing them as PCIe devices under a NPU PHB type. Each PCIe device has a configuration space implemented in software which indicates the base address of the DL/TL/PL registers required by the device drivers. It also presents one LSI per device which is used to signal device drivers of changes in device status. The configuration space also adds a vendor specific capability which is used primarily by device drivers to power on an train the IBM PHY. Signed-off-by: Gavin Shan <gwshan@linux.vnet.ibm.com> Signed-off-by: Alistair Popple <alistair@popple.id.au> Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'hw/npu-hw-procedures.c')
-rw-r--r--hw/npu-hw-procedures.c598
1 files changed, 598 insertions, 0 deletions
diff --git a/hw/npu-hw-procedures.c b/hw/npu-hw-procedures.c
new file mode 100644
index 00000000..118ed6d7
--- /dev/null
+++ b/hw/npu-hw-procedures.c
@@ -0,0 +1,598 @@
+/* Copyright 2013-2015 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 <io.h>
+#include <timebase.h>
+#include <pci.h>
+#include <interrupts.h>
+#include <lock.h>
+#include <npu-regs.h>
+#include <npu.h>
+#include <xscom.h>
+
+typedef uint32_t (*step)(struct npu_dev *);
+
+struct procedure {
+ const char *name;
+ step steps[];
+};
+
+#define DEFINE_PROCEDURE(NAME, STEPS...) \
+ struct procedure procedure_##NAME = \
+ {.name = #NAME, .steps = {NAME, ##STEPS}}
+
+#define PROCEDURE_INPROGRESS (1 << 31)
+#define PROCEDURE_COMPLETE (1 << 30)
+#define PROCEDURE_NEXT (1 << 29)
+#define PROCEDURE_FAILED 2
+#define PROCEDURE_ABORTED 3
+#define PROCEDURE_UNSUPPORTED 4
+
+/* Mask defining which status bits we want to expose */
+#define PROCEDURE_STATUS_MASK 0xc000000f
+
+/* Accesors for PHY registers. These can be done either via MMIO or SCOM. */
+static bool pl_use_scom = 1;
+static void phy_write(struct npu_dev *npu_dev, uint64_t addr, uint32_t val)
+{
+ if (pl_use_scom)
+ xscom_write(npu_dev->npu->chip_id, npu_dev->pl_xscom_base | addr, val);
+ else
+ out_be16((void *) npu_dev->pl_base + PL_MMIO_ADDR(addr), val);
+}
+
+static uint16_t phy_read(struct npu_dev *npu_dev, uint64_t addr)
+{
+ uint64_t val;
+
+ if (pl_use_scom)
+ xscom_read(npu_dev->npu->chip_id, npu_dev->pl_xscom_base + addr, &val);
+ else
+ val = in_be16((void *) npu_dev->pl_base + PL_MMIO_ADDR(addr));
+
+ return val & 0xffff;
+}
+
+/* The DL registers can be accessed indirectly via the NTL */
+static void dl_write(struct npu_dev *npu_dev, uint32_t addr, uint32_t val)
+{
+ xscom_write(npu_dev->npu->chip_id,
+ npu_dev->xscom + NX_DL_REG_ADDR, addr);
+ xscom_write(npu_dev->npu->chip_id,
+ npu_dev->xscom + NX_DL_REG_DATA, val);
+}
+
+static uint64_t __unused dl_read(struct npu_dev *npu_dev, uint32_t addr)
+{
+ uint64_t val;
+
+ xscom_write(npu_dev->npu->chip_id,
+ npu_dev->xscom + NX_DL_REG_ADDR, addr);
+ xscom_read(npu_dev->npu->chip_id,
+ npu_dev->xscom + NX_DL_REG_DATA, &val);
+ return val;
+}
+
+/* Our hardware bits are backwards here. The lane vectors are 16-bit
+ * values represented in IBM bit ordering. This means lane 0 is
+ * represented by bit 15 in most of the registers. Internally we keep
+ * this sane (ie. npu_dev->lane_mask[0] == lane 0) as we need sane
+ * numbering for set_lane_reg() anyway. */
+static uint32_t phy_lane_mask(struct npu_dev *npu_dev)
+{
+ /* We only train 8 lanes at a time so we don't do a full
+ * bit-swap */
+ assert(npu_dev->lane_mask == 0xff00 || npu_dev->lane_mask == 0xff);
+
+ return ~npu_dev->lane_mask & 0xffff;
+}
+
+static void set_lane_reg(struct npu_dev *npu_dev, uint64_t base_reg,
+ uint64_t data, uint64_t mask)
+{
+ uint64_t val, i;
+ uint32_t lane_mask = npu_dev->lane_mask;
+
+ for (i = 0; i <= 23; i++) {
+ if (lane_mask & (1ul << i)) {
+ uint64_t tx_rxcal_reg = base_reg + (i << 32);
+ val = phy_read(npu_dev, tx_rxcal_reg);
+ val = (val & ~mask) | data;
+ phy_write(npu_dev, tx_rxcal_reg, val);
+ }
+ }
+}
+
+static uint32_t stop(struct npu_dev *npu_dev __unused)
+{
+ return PROCEDURE_COMPLETE | PROCEDURE_ABORTED;
+}
+DEFINE_PROCEDURE(stop);
+
+static uint32_t nop(struct npu_dev *npu_dev __unused)
+{
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(nop);
+
+/* Procedure 1.2.1 (RESET_NPU_DL) from opt_programmerguide.odt. Also
+ * incorporates AT reset. */
+static uint32_t reset_npu_dl(struct npu_dev *npu_dev)
+{
+ void *ntl_base = (void *) npu_dev->bar.base;
+ uint64_t val;
+
+ /* Assert NPU reset */
+ val = in_be64(ntl_base + NTL_CONTROL);
+ val |= NTL_CONTROL_RESET;
+ out_be64(ntl_base + NTL_CONTROL, val);
+
+ /* Put the Nvidia logic in reset */
+ dl_write(npu_dev, NDL_CONTROL, 0xe8000000);
+
+ /* Release Nvidia logic from reset */
+ dl_write(npu_dev, NDL_CONTROL, 0);
+
+ /* Release NPU from reset */
+ val &= ~NTL_CONTROL_RESET;
+ out_be64(ntl_base + NTL_CONTROL, val);
+
+ /* Setup up TL credits */
+ out_be64(ntl_base + TL_CMD_CR, PPC_BIT(0));
+ out_be64(ntl_base + TL_CMD_D_CR, PPC_BIT(0));
+ out_be64(ntl_base + TL_RSP_CR, PPC_BIT(15));
+ out_be64(ntl_base + TL_RSP_D_CR, PPC_BIT(15));
+
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(reset_npu_dl);
+
+/* Procedures 1.2.3 (reset_lanes) & 1.2.4
+ * (io_register_write_reset_values) */
+static uint32_t phy_reset(struct npu_dev *npu_dev)
+{
+ uint16_t val;
+
+ /* Lower run_lane inputs for lanes to be reset */
+ val = phy_read(npu_dev, RX_RUN_LANE_VEC_0_15);
+ val &= ~phy_lane_mask(npu_dev);
+ phy_write(npu_dev, RX_RUN_LANE_VEC_0_15, val);
+
+ return PROCEDURE_NEXT;
+}
+
+static uint32_t phy_reset_wait(struct npu_dev *npu_dev)
+{
+ uint16_t val;
+
+ /* Wait for lane busy outputs to go to zero for lanes to be
+ * reset */
+ val = phy_read(npu_dev, RX_LANE_BUSY_VEC_0_15);
+ if (val & phy_lane_mask(npu_dev))
+ return PROCEDURE_INPROGRESS;
+
+ return PROCEDURE_NEXT;
+}
+
+static uint32_t phy_reset_complete(struct npu_dev *npu_dev)
+{
+ uint16_t val;
+ uint32_t lane_mask = phy_lane_mask(npu_dev);
+
+ /* Set ioreset_vec for the desired lanes bit positions */
+ val = phy_read(npu_dev, RX_IORESET_VEC_0_15);
+ phy_write(npu_dev, RX_IORESET_VEC_0_15, val | lane_mask);
+
+ val = phy_read(npu_dev, TX_IORESET_VEC_0_15);
+ phy_write(npu_dev, TX_IORESET_VEC_0_15, val | lane_mask);
+
+ /* Clear ioreset_vec */
+ val = phy_read(npu_dev, RX_IORESET_VEC_0_15);
+ phy_write(npu_dev, RX_IORESET_VEC_0_15, val & ~lane_mask);
+
+ val = phy_read(npu_dev, TX_IORESET_VEC_0_15);
+ phy_write(npu_dev, TX_IORESET_VEC_0_15, val & ~lane_mask);
+
+ /* Reset RX phase rotators */
+ set_lane_reg(npu_dev, RX_PR_CNTL_PL, RX_PR_RESET, RX_PR_RESET);
+ set_lane_reg(npu_dev, RX_PR_CNTL_PL, 0, RX_PR_RESET);
+
+ /* Restore registers from scominit that may have changed */
+ set_lane_reg(npu_dev, RX_PR_MODE, 0x8, RX_PR_PHASE_STEP);
+ set_lane_reg(npu_dev, RX_A_DAC_CNTL,
+ 0x7 << MASK_TO_LSH(RX_PR_IQ_RES_SEL),
+ RX_PR_IQ_RES_SEL);
+ set_lane_reg(npu_dev, TX_MODE1_PL, 0, TX_LANE_PDWN);
+ set_lane_reg(npu_dev, RX_BANK_CONTROLS, 0, RX_LANE_ANA_PDWN);
+ set_lane_reg(npu_dev, RX_MODE, 0, RX_LANE_DIG_PDWN);
+
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(phy_reset, phy_reset_wait, phy_reset_complete);
+
+/* Round a fixed decimal number. Frac is the number of fractional
+ * bits */
+static uint32_t round(uint32_t val, int frac)
+{
+ if (val >> (frac - 1) & 0x1)
+ return (val >> frac) + 1;
+ else
+ return val >> frac;
+}
+
+#define ZCAL_MIN (10 << 3)
+#define ZCAL_MAX (40 << 3)
+#define ZCAL_K0 0x0
+#define ZCAL_M 128
+/* TODO: add a test case for the following values:
+
+ Initial values:
+ zcal_n = 0xda;
+ zcal_p = 0xc7;
+
+ Results:
+ pre_p = 0x0
+ pre_n = 0x0
+ margin_p = 0x0
+ margin_n = 0x0
+ total_en_p = 0x32
+ total_en_n = 0x37
+ */
+
+static uint32_t phy_tx_zcal(struct npu_dev *npu_dev)
+{
+ uint64_t val;
+
+ if (npu_dev->index < 2 && npu_dev->npu->tx_zcal_complete[0])
+ return PROCEDURE_COMPLETE;
+
+ if (npu_dev->index >= 2 && npu_dev->npu->tx_zcal_complete[1])
+ return PROCEDURE_COMPLETE;
+
+ /* Start calibration */
+ val = phy_read(npu_dev, TX_IMPCAL_SWO1_PB);
+ val &= TX_ZCAL_SWO_EN;
+ phy_write(npu_dev, TX_IMPCAL_SWO1_PB, val);
+ phy_write(npu_dev, TX_IMPCAL_SWO2_PB, 0x50 << 2);
+ val = phy_read(npu_dev, TX_IMPCAL_PB);
+ val |= TX_ZCAL_REQ;
+ phy_write(npu_dev, TX_IMPCAL_PB, val);
+
+ return PROCEDURE_NEXT;
+}
+
+static uint32_t phy_tx_zcal_wait(struct npu_dev *npu_dev)
+{
+ uint64_t val;
+
+ val = phy_read(npu_dev, TX_IMPCAL_PB);
+ if (!(val & TX_ZCAL_DONE))
+ return PROCEDURE_INPROGRESS;
+
+ if (val & TX_ZCAL_ERROR)
+ return PROCEDURE_COMPLETE | PROCEDURE_FAILED;
+
+ return PROCEDURE_NEXT;
+}
+
+static uint32_t phy_tx_zcal_calculate(struct npu_dev *npu_dev)
+{
+ uint64_t val;
+ uint64_t zcal_n;
+ uint64_t zcal_p;
+ uint64_t margin_n;
+ uint64_t margin_p;
+ uint64_t pre_n;
+ uint64_t pre_p;
+ uint64_t total_en_n;
+ uint64_t total_en_p;
+
+ val = phy_read(npu_dev, TX_IMPCAL_NVAL_PB);
+ zcal_n = GETFIELD(TX_ZCAL_N, val);
+ val = phy_read(npu_dev, TX_IMPCAL_PVAL_PB);
+ zcal_p = GETFIELD(TX_ZCAL_P, val);
+
+ if ((zcal_n < ZCAL_MIN) || (zcal_n > ZCAL_MAX) ||
+ (zcal_p < ZCAL_MIN) || (zcal_p > ZCAL_MAX))
+ return PROCEDURE_COMPLETE | PROCEDURE_FAILED;
+
+ margin_n = (0x80 - ZCAL_M) * zcal_n / 2;
+ margin_p = (0x80 - ZCAL_M) * zcal_p / 2;
+ pre_n = (((0x80 * zcal_n) - (2 * margin_n)) * ZCAL_K0) / 0x80;
+ pre_p = (((0x80 * zcal_p) - (2 * margin_p)) * ZCAL_K0) / 0x80;
+
+ total_en_n = 0x80 * zcal_n - (2 * margin_n) - (pre_n & 1023);
+ total_en_p = 0x80 * zcal_p - (2 * margin_p) - (pre_p & 1023);
+
+ pre_p = round(pre_p, 9);
+ pre_n = round(pre_n, 9);
+ margin_p = round(margin_p, 9);
+ margin_n = round(margin_n, 9);
+ total_en_p = round(total_en_p, 9);
+ total_en_n = round(total_en_n, 9);
+
+ val = SETFIELD(TX_FFE_TOTAL_ENABLE_N_ENC, 0, total_en_n);
+ val = SETFIELD(TX_FFE_TOTAL_ENABLE_P_ENC, val, total_en_p);
+ phy_write(npu_dev, TX_FFE_TOTAL_2RSTEP_EN, val);
+
+ val = SETFIELD(TX_FFE_PRE_N_SEL_ENC, 0, pre_n);
+ val = SETFIELD(TX_FFE_PRE_P_SEL_ENC, val, pre_p);
+ phy_write(npu_dev, TX_FFE_PRE_2RSTEP_SEL, val);
+
+ val = SETFIELD(TX_FFE_MARGIN_PD_N_SEL_ENC, 0, margin_n);
+ val = SETFIELD(TX_FFE_MARGIN_PU_P_SEL_ENC, val, margin_p);
+ phy_write(npu_dev, TX_FFE_MARGIN_2RSTEP_SEL, val);
+
+ if (npu_dev->index < 2)
+ npu_dev->npu->tx_zcal_complete[0] = true;
+ else
+ npu_dev->npu->tx_zcal_complete[1] = true;
+
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(phy_tx_zcal, phy_tx_zcal_wait, phy_tx_zcal_calculate);
+
+static uint32_t phy_enable_tx_rxcal(struct npu_dev *npu_dev)
+{
+ /* Turn common mode on */
+ set_lane_reg(npu_dev, TX_MODE2_PL, TX_RXCAL, TX_RXCAL);
+
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(phy_enable_tx_rxcal);
+
+static uint32_t phy_disable_tx_rxcal(struct npu_dev *npu_dev)
+{
+ /* Turn common mode off */
+ set_lane_reg(npu_dev, TX_MODE2_PL, 0, TX_RXCAL);
+
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(phy_disable_tx_rxcal);
+
+static uint32_t phy_rx_dccal(struct npu_dev *npu_dev)
+{
+ if (phy_read(npu_dev, RX_LANE_BUSY_VEC_0_15)
+ & ~phy_read(npu_dev, RX_INIT_DONE_VEC_0_15))
+ return PROCEDURE_INPROGRESS;
+
+ return PROCEDURE_NEXT;
+}
+
+static uint32_t phy_rx_dccal_start(struct npu_dev *npu_dev)
+{
+ uint64_t val;
+
+ /* Save EO step control */
+ val = phy_read(npu_dev, RX_EO_STEP_CNTL_PG);
+ npu_dev->procedure_data = val;
+
+ phy_write(npu_dev, RX_EO_STEP_CNTL_PG,
+ RX_EO_ENABLE_LATCH_OFFSET_CAL
+ | RX_EO_ENABLE_CM_COARSE_CAL);
+
+ val = phy_read(npu_dev, RX_RECAL_ABORT_VEC_0_15);
+ val |= phy_lane_mask(npu_dev);
+ phy_write(npu_dev, RX_RECAL_ABORT_VEC_0_15, val);
+
+ val = phy_read(npu_dev, RX_RUN_LANE_VEC_0_15);
+ val |= phy_lane_mask(npu_dev);
+ phy_write(npu_dev, RX_RUN_LANE_VEC_0_15, val);
+
+ return PROCEDURE_NEXT;
+}
+
+static uint32_t phy_rx_dccal_complete(struct npu_dev *npu_dev)
+{
+ /* Poll for completion on relevant lanes */
+ if ((phy_read(npu_dev, RX_INIT_DONE_VEC_0_15) & phy_lane_mask(npu_dev))
+ != phy_lane_mask(npu_dev))
+ return PROCEDURE_INPROGRESS;
+
+ return PROCEDURE_NEXT;
+}
+
+static uint32_t phy_rx_dccal_fifo_init(struct npu_dev *npu_dev)
+{
+ uint64_t val;
+
+ val = phy_read(npu_dev, RX_RUN_LANE_VEC_0_15);
+ val &= ~phy_lane_mask(npu_dev);
+ phy_write(npu_dev, RX_RUN_LANE_VEC_0_15, val);
+
+ /* Turn off recal abort */
+ val = phy_read(npu_dev, RX_RECAL_ABORT_VEC_0_15);
+ val &= ~phy_lane_mask(npu_dev);
+ phy_write(npu_dev, RX_RECAL_ABORT_VEC_0_15, val);
+
+ /* Restore original settings */
+ phy_write(npu_dev, RX_EO_STEP_CNTL_PG, npu_dev->procedure_data);
+
+ /* FIFO Init */
+ set_lane_reg(npu_dev, TX_MODE2_PL, 0, TX_UNLOAD_CLK_DISABLE);
+ set_lane_reg(npu_dev, TX_CNTL_STAT2, TX_FIFO_INIT, TX_FIFO_INIT);
+ set_lane_reg(npu_dev, TX_MODE2_PL, TX_UNLOAD_CLK_DISABLE,
+ TX_UNLOAD_CLK_DISABLE);
+
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(phy_rx_dccal, phy_rx_dccal_start, phy_rx_dccal_complete,
+ phy_rx_dccal_fifo_init);
+
+static uint32_t phy_rx_training(struct npu_dev *npu_dev)
+{
+ uint16_t val;
+
+ if (!npu_dev->procedure_data) {
+ val = phy_read(npu_dev, RX_RUN_LANE_VEC_0_15);
+ val |= phy_lane_mask(npu_dev);
+ phy_write(npu_dev, RX_RUN_LANE_VEC_0_15, val);
+ }
+
+ npu_dev->procedure_data++;
+ if (npu_dev->procedure_data >= 1000000)
+ return PROCEDURE_COMPLETE | PROCEDURE_FAILED;
+
+ val = phy_read(npu_dev, RX_RUN_LANE_VEC_0_15);
+ if ((val & phy_lane_mask(npu_dev)) != phy_lane_mask(npu_dev))
+ return PROCEDURE_INPROGRESS;
+
+ return PROCEDURE_COMPLETE;
+}
+DEFINE_PROCEDURE(phy_rx_training);
+
+static struct procedure *npu_procedures[] = {
+ &procedure_stop,
+ &procedure_nop,
+ NULL,
+ NULL,
+ &procedure_phy_reset,
+ &procedure_phy_tx_zcal,
+ &procedure_phy_rx_dccal,
+ &procedure_phy_enable_tx_rxcal,
+ &procedure_phy_disable_tx_rxcal,
+ &procedure_phy_rx_training,
+ &procedure_reset_npu_dl,
+
+ /* Place holders for pre-terminate and terminate procedures */
+ &procedure_nop,
+ &procedure_nop};
+
+/* Run a procedure step(s) and return status */
+static uint32_t get_procedure_status(struct npu_dev *dev)
+{
+ uint32_t result;
+ uint16_t procedure = dev->procedure_number;
+ uint16_t step = dev->procedure_step;
+ const char *name = npu_procedures[procedure]->name;
+
+ do {
+ result = npu_procedures[procedure]->steps[step](dev);
+
+ if (result & PROCEDURE_NEXT) {
+ step++;
+ NPUDEVINF(dev, "Running procedure %s step %d\n", name, step);
+ }
+ } while (result & PROCEDURE_NEXT);
+
+ dev->procedure_step = step;
+
+ if (result & PROCEDURE_COMPLETE)
+ NPUDEVINF(dev, "Procedure %s complete\n", name);
+ else if (mftb() > dev->procedure_tb + msecs_to_tb(100)) {
+ NPUDEVINF(dev, "Procedure %s timed out\n", name);
+ result = PROCEDURE_COMPLETE | PROCEDURE_FAILED;
+ }
+
+ /* Mask off internal state bits */
+ dev->procedure_status = result & PROCEDURE_STATUS_MASK;
+
+ return dev->procedure_status;
+}
+
+int64_t npu_dev_procedure_read(struct npu_dev_trap *trap,
+ uint32_t offset,
+ uint32_t size,
+ uint32_t *data)
+{
+ struct npu_dev *dev = trap->dev;
+ int64_t rc = OPAL_SUCCESS;
+
+ if (size != 4) {
+ /* Short config reads are not supported */
+ NPUDEVERR(dev, "Short read of procedure register\n");
+ return OPAL_PARAMETER;
+ }
+
+ offset -= trap->start;
+ *data = 0;
+
+ switch (offset) {
+ case 0:
+ /* Only run the procedure if not already complete */
+ if (dev->procedure_status & PROCEDURE_COMPLETE)
+ *data = dev->procedure_status;
+ else
+ *data = get_procedure_status(dev);
+
+ break;
+
+ case 4:
+ *data = dev->procedure_number;
+ break;
+
+ default:
+ NPUDEVERR(dev, "Invalid vendor specific offset 0x%08x\n",
+ offset);
+ rc = OPAL_PARAMETER;
+ }
+
+ return rc;
+}
+
+int64_t npu_dev_procedure_write(struct npu_dev_trap *trap,
+ uint32_t offset,
+ uint32_t size,
+ uint32_t data)
+{
+ struct npu_dev *dev = trap->dev;
+ const char *name;
+ int64_t rc = OPAL_SUCCESS;
+
+ if (size != 4) {
+ /* Short config writes are not supported */
+ NPUDEVERR(dev, "Short read of procedure register\n");
+ return OPAL_PARAMETER;
+ }
+
+ offset -= trap->start;
+
+ switch (offset) {
+ case 0:
+ /* We ignore writes to the status register */
+ NPUDEVINF(dev, "Ignoring writes to status register\n");
+ break;
+
+ case 4:
+ if (data >= ARRAY_SIZE(npu_procedures) ||
+ !npu_procedures[data]) {
+ NPUDEVINF(dev, "Unsupported procedure number %d\n", data);
+ dev->procedure_status = PROCEDURE_COMPLETE
+ | PROCEDURE_UNSUPPORTED;
+ break;
+ }
+
+ name = npu_procedures[data]->name;
+ if (dev->procedure_number == data
+ && !(dev->procedure_status & PROCEDURE_COMPLETE))
+ NPUDEVINF(dev, "Restarting procuedure %s\n", name);
+ else
+ NPUDEVINF(dev, "Starting procedure %s\n", name);
+
+ dev->procedure_status = PROCEDURE_INPROGRESS;
+ dev->procedure_number = data;
+ dev->procedure_step = 0;
+ dev->procedure_data = 0;
+ dev->procedure_tb = mftb();
+ break;
+
+ default:
+ NPUDEVINF(dev, "Invalid vendor specific offset 0x%08x\n", offset);
+ rc = OPAL_PARAMETER;
+ }
+
+ return rc;
+}
OpenPOWER on IntegriCloud