978 lines
28 KiB
C
978 lines
28 KiB
C
/* 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.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "FIRENZE-PCI: " fmt
|
|
#include <skiboot.h>
|
|
#include <device.h>
|
|
#include <fsp.h>
|
|
#include <lock.h>
|
|
#include <timer.h>
|
|
#include <xscom.h>
|
|
#include <pci-cfg.h>
|
|
#include <pci.h>
|
|
#include <pci-slot.h>
|
|
#include <phb3.h>
|
|
#include <chip.h>
|
|
#include <i2c.h>
|
|
|
|
#include "ibm-fsp.h"
|
|
#include "lxvpd.h"
|
|
|
|
/* Dump PCI slots before sending to FSP */
|
|
#define FIRENZE_PCI_INVENTORY_DUMP
|
|
|
|
/*
|
|
* Firenze PCI slot states to override the default set.
|
|
* Refer to pci-slot.h for the default PCI state set
|
|
* when you're going to change below values.
|
|
*/
|
|
#define FIRENZE_PCI_SLOT_NORMAL PCI_SLOT_STATE_NORMAL
|
|
#define FIRENZE_PCI_SLOT_LINK PCI_SLOT_STATE_LINK
|
|
#define FIRENZE_PCI_SLOT_LINK_START (FIRENZE_PCI_SLOT_LINK + 1)
|
|
#define FIRENZE_PCI_SLOT_HRESET PCI_SLOT_STATE_HRESET
|
|
#define FIRENZE_PCI_SLOT_HRESET_START (FIRENZE_PCI_SLOT_HRESET + 1)
|
|
#define FIRENZE_PCI_SLOT_FRESET PCI_SLOT_STATE_FRESET
|
|
#define FIRENZE_PCI_SLOT_FRESET_START (FIRENZE_PCI_SLOT_FRESET + 1)
|
|
#define FIRENZE_PCI_SLOT_FRESET_WAIT_RSP (FIRENZE_PCI_SLOT_FRESET + 2)
|
|
#define FIRENZE_PCI_SLOT_FRESET_DELAY (FIRENZE_PCI_SLOT_FRESET + 3)
|
|
#define FIRENZE_PCI_SLOT_FRESET_POWER_STATE (FIRENZE_PCI_SLOT_FRESET + 4)
|
|
#define FIRENZE_PCI_SLOT_FRESET_POWER_OFF (FIRENZE_PCI_SLOT_FRESET + 5)
|
|
#define FIRENZE_PCI_SLOT_FRESET_POWER_ON (FIRENZE_PCI_SLOT_FRESET + 6)
|
|
#define FIRENZE_PCI_SLOT_PERST_DEASSERT (FIRENZE_PCI_SLOT_FRESET + 7)
|
|
#define FIRENZE_PCI_SLOT_PERST_DELAY (FIRENZE_PCI_SLOT_FRESET + 8)
|
|
#define FIRENZE_PCI_SLOT_GPOWER PCI_SLOT_STATE_GPOWER
|
|
#define FIRENZE_PCI_SLOT_GPOWER_START (FIRENZE_PCI_SLOT_GPOWER + 1)
|
|
#define FIRENZE_PCI_SLOT_SPOWER PCI_SLOT_STATE_SPOWER
|
|
#define FIRENZE_PCI_SLOT_SPOWER_START (FIRENZE_PCI_SLOT_SPOWER + 1)
|
|
#define FIRENZE_PCI_SLOT_SPOWER_DONE (FIRENZE_PCI_SLOT_SPOWER + 2)
|
|
|
|
/* Timeout for power status */
|
|
#define FIRENZE_PCI_SLOT_RETRIES 500
|
|
#define FIRENZE_PCI_SLOT_DELAY 10 /* ms */
|
|
#define FIRENZE_PCI_I2C_TIMEOUT 500 /* ms */
|
|
|
|
/*
|
|
* Need figure out more stuff later: LED and presence
|
|
* detection sensors are accessed from PSI/FSP.
|
|
*/
|
|
struct firenze_pci_slot {
|
|
struct lxvpd_pci_slot lxvpd_slot; /* LXVPD slot data */
|
|
|
|
/* Next slot state */
|
|
uint32_t next_state;
|
|
|
|
/* Power management */
|
|
struct i2c_bus *i2c_bus; /* Where MAX5961 seats */
|
|
struct i2c_request *req; /* I2C request message */
|
|
uint8_t i2c_rw_buf[8]; /* I2C read/write buffer */
|
|
uint8_t power_mask; /* Bits for power status */
|
|
uint8_t power_on; /* Bits for power on */
|
|
uint8_t power_off; /* Bits for power off */
|
|
uint8_t *power_status; /* Last power status */
|
|
uint16_t perst_reg; /* PERST config register */
|
|
uint16_t perst_bit; /* PERST bit */
|
|
};
|
|
|
|
struct firenze_pci_slot_info {
|
|
uint8_t index;
|
|
const char *label;
|
|
uint8_t external_power_mgt;
|
|
uint8_t inband_perst;
|
|
uint8_t chip_id;
|
|
uint8_t master_id;
|
|
uint8_t port_id;
|
|
uint8_t slave_addr;
|
|
uint8_t channel;
|
|
uint8_t power_status;
|
|
uint8_t buddy;
|
|
};
|
|
|
|
struct firenze_pci_slot_fixup_info {
|
|
const char *label;
|
|
uint8_t reg;
|
|
uint8_t val;
|
|
};
|
|
|
|
struct firenze_pci_inv {
|
|
uint32_t hw_proc_id;
|
|
uint16_t slot_idx;
|
|
uint16_t reserved;
|
|
uint16_t vendor_id;
|
|
uint16_t device_id;
|
|
uint16_t subsys_vendor_id;
|
|
uint16_t subsys_device_id;
|
|
};
|
|
|
|
struct firenze_pci_inv_data {
|
|
uint32_t version; /* currently 1 */
|
|
uint32_t num_entries;
|
|
uint32_t entry_size;
|
|
uint32_t entry_offset;
|
|
struct firenze_pci_inv entries[];
|
|
};
|
|
|
|
/*
|
|
* Note: According to Tuleta system workbook, I didn't figure
|
|
* out the I2C mapping info for slot C14/C15.
|
|
*/
|
|
static struct firenze_pci_inv_data *firenze_inv_data;
|
|
static uint32_t firenze_inv_cnt;
|
|
static struct firenze_pci_slot_info firenze_pci_slots[] = {
|
|
{ 0x0B, "C7", 1, 1, 0, 1, 0, 0x35, 1, 0xAA, 0 },
|
|
{ 0x11, "C14", 0, 1, 0, 0, 0, 0x00, 0, 0xAA, 1 },
|
|
{ 0x0F, "C11", 1, 1, 0, 1, 0, 0x32, 1, 0xAA, 2 },
|
|
{ 0x10, "C12", 1, 1, 0, 1, 0, 0x39, 0, 0xAA, 3 },
|
|
{ 0x0A, "C6", 1, 1, 0, 1, 0, 0x35, 0, 0xAA, 0 },
|
|
{ 0x12, "C15", 0, 1, 0, 0, 0, 0x00, 0, 0xAA, 5 },
|
|
{ 0x01, "USB", 0, 0, 0, 0, 0, 0x00, 0, 0xAA, 6 },
|
|
{ 0x0C, "C8", 1, 1, 0, 1, 0, 0x36, 0, 0xAA, 7 },
|
|
{ 0x0D, "C9", 1, 1, 0, 1, 0, 0x36, 1, 0xAA, 7 },
|
|
{ 0x0E, "C10", 1, 1, 0, 1, 0, 0x32, 0, 0xAA, 2 },
|
|
{ 0x09, "C5", 1, 1, 0x10, 1, 0, 0x39, 1, 0xAA, 10 },
|
|
{ 0x08, "C4", 1, 1, 0x10, 1, 0, 0x39, 0, 0xAA, 10 },
|
|
{ 0x07, "C3", 1, 1, 0x10, 1, 0, 0x3A, 1, 0xAA, 12 },
|
|
{ 0x06, "C2", 1, 1, 0x10, 1, 0, 0x3A, 0, 0xAA, 12 }
|
|
};
|
|
|
|
/*
|
|
* I2C power controller register fix up table. Not sure what they do, but
|
|
* they seem to relate to the fast-trip setpoint.
|
|
*/
|
|
static struct firenze_pci_slot_fixup_info firenze_pci_slot_fixup_tbl[] = {
|
|
{ "C3", 0x5e, 0xfb },
|
|
{ "C3", 0x5b, 0xff },
|
|
{ "C5", 0x5e, 0xfb },
|
|
{ "C5", 0x5b, 0xff },
|
|
{ "C6", 0x5e, 0xfa },
|
|
{ "C6", 0x5a, 0xff },
|
|
{ "C6", 0x5b, 0xff },
|
|
{ "C7", 0x5e, 0xfa },
|
|
{ "C7", 0x5a, 0xff },
|
|
{ "C7", 0x5b, 0xff }
|
|
};
|
|
|
|
static void firenze_pci_add_inventory(struct phb *phb,
|
|
struct pci_device *pd)
|
|
{
|
|
struct lxvpd_pci_slot *lxvpd_slot;
|
|
struct firenze_pci_inv *entry;
|
|
struct proc_chip *chip;
|
|
size_t size;
|
|
bool need_init = false;
|
|
|
|
/*
|
|
* Do we need to add that to the FSP inventory for power
|
|
* management?
|
|
*
|
|
* For now, we only add devices that:
|
|
*
|
|
* - Are function 0
|
|
* - Are not an RC or a downstream bridge
|
|
* - Have a direct parent that has a slot entry
|
|
* - Slot entry says pluggable
|
|
* - Aren't an upstream switch that has slot info
|
|
*/
|
|
if (!pd || !pd->parent)
|
|
return;
|
|
if (pd->bdfn & 7)
|
|
return;
|
|
if (pd->dev_type == PCIE_TYPE_ROOT_PORT ||
|
|
pd->dev_type == PCIE_TYPE_SWITCH_DNPORT)
|
|
return;
|
|
if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
|
|
pd->slot && pd->slot->data)
|
|
return;
|
|
if (!pd->parent->slot ||
|
|
!pd->parent->slot->data)
|
|
return;
|
|
lxvpd_slot = pd->parent->slot->data;
|
|
if (!lxvpd_slot->pluggable)
|
|
return;
|
|
|
|
/* Check if we need to do some (Re)allocation */
|
|
if (!firenze_inv_data ||
|
|
firenze_inv_data->num_entries == firenze_inv_cnt) {
|
|
need_init = !firenze_inv_data;
|
|
|
|
/* (Re)allocate the block to the new size */
|
|
firenze_inv_cnt += 4;
|
|
size = sizeof(struct firenze_pci_inv_data) +
|
|
sizeof(struct firenze_pci_inv) * firenze_inv_cnt;
|
|
firenze_inv_data = realloc(firenze_inv_data, size);
|
|
}
|
|
|
|
/* Initialize the header for a new inventory */
|
|
if (need_init) {
|
|
firenze_inv_data->version = 1;
|
|
firenze_inv_data->num_entries = 0;
|
|
firenze_inv_data->entry_size =
|
|
sizeof(struct firenze_pci_inv);
|
|
firenze_inv_data->entry_offset =
|
|
offsetof(struct firenze_pci_inv_data, entries);
|
|
}
|
|
|
|
/* Append slot entry */
|
|
entry = &firenze_inv_data->entries[firenze_inv_data->num_entries++];
|
|
chip = get_chip(dt_get_chip_id(phb->dt_node));
|
|
if (!chip) {
|
|
/**
|
|
* @fwts-label FirenzePCIInventory
|
|
* @fwts-advice Device tree didn't contain enough information
|
|
* to correctly report back PCI inventory. Service processor
|
|
* is likely to be missing information about what hardware
|
|
* is physically present in the machine.
|
|
*/
|
|
prlog(PR_ERR, "No chip device node for PHB%04x\n",
|
|
phb->opal_id);
|
|
return;
|
|
}
|
|
|
|
entry->hw_proc_id = chip->pcid;
|
|
entry->reserved = 0;
|
|
if (pd->parent &&
|
|
pd->parent->slot &&
|
|
pd->parent->slot->data) {
|
|
lxvpd_slot = pd->parent->slot->data;
|
|
entry->slot_idx = lxvpd_slot->slot_index;
|
|
}
|
|
|
|
pci_cfg_read16(phb, pd->bdfn, PCI_CFG_VENDOR_ID, &entry->vendor_id);
|
|
pci_cfg_read16(phb, pd->bdfn, PCI_CFG_DEVICE_ID, &entry->device_id);
|
|
if (pd->is_bridge) {
|
|
int64_t ssvc = pci_find_cap(phb, pd->bdfn,
|
|
PCI_CFG_CAP_ID_SUBSYS_VID);
|
|
if (ssvc <= 0) {
|
|
entry->subsys_vendor_id = 0xffff;
|
|
entry->subsys_device_id = 0xffff;
|
|
} else {
|
|
pci_cfg_read16(phb, pd->bdfn,
|
|
ssvc + PCICAP_SUBSYS_VID_VENDOR,
|
|
&entry->subsys_vendor_id);
|
|
pci_cfg_read16(phb, pd->bdfn,
|
|
ssvc + PCICAP_SUBSYS_VID_DEVICE,
|
|
&entry->subsys_device_id);
|
|
}
|
|
} else {
|
|
pci_cfg_read16(phb, pd->bdfn, PCI_CFG_SUBSYS_VENDOR_ID,
|
|
&entry->subsys_vendor_id);
|
|
pci_cfg_read16(phb, pd->bdfn, PCI_CFG_SUBSYS_ID,
|
|
&entry->subsys_device_id);
|
|
}
|
|
}
|
|
|
|
static void firenze_dump_pci_inventory(void)
|
|
{
|
|
#ifdef FIRENZE_PCI_INVENTORY_DUMP
|
|
struct firenze_pci_inv *e;
|
|
uint32_t i;
|
|
|
|
if (!firenze_inv_data)
|
|
return;
|
|
|
|
prlog(PR_INFO, "Dumping Firenze PCI inventory\n");
|
|
prlog(PR_INFO, "HWP SLT VDID DVID SVID SDID\n");
|
|
prlog(PR_INFO, "---------------------------\n");
|
|
for (i = 0; i < firenze_inv_data->num_entries; i++) {
|
|
e = &firenze_inv_data->entries[i];
|
|
|
|
prlog(PR_INFO, "%03d %03d %04x %04x %04x %04x\n",
|
|
e->hw_proc_id, e->slot_idx,
|
|
e->vendor_id, e->device_id,
|
|
e->subsys_vendor_id, e->subsys_device_id);
|
|
}
|
|
#endif /* FIRENZE_PCI_INVENTORY_DUMP */
|
|
}
|
|
|
|
void firenze_pci_send_inventory(void)
|
|
{
|
|
uint64_t base, abase, end, aend, offset;
|
|
int64_t rc;
|
|
|
|
if (!firenze_inv_data)
|
|
return;
|
|
|
|
/* Dump the inventory */
|
|
prlog(PR_INFO, "Sending %d inventory to FSP\n",
|
|
firenze_inv_data->num_entries);
|
|
firenze_dump_pci_inventory();
|
|
|
|
/* Memory location for inventory */
|
|
base = (uint64_t)firenze_inv_data;
|
|
end = base +
|
|
sizeof(struct firenze_pci_inv_data) +
|
|
firenze_inv_data->num_entries * firenze_inv_data->entry_size;
|
|
abase = base & ~0xffful;
|
|
aend = (end + 0xffful) & ~0xffful;
|
|
offset = PSI_DMA_PCIE_INVENTORY + (base & 0xfff);
|
|
|
|
/* We can only accomodate so many entries in the PSI map */
|
|
if ((aend - abase) > PSI_DMA_PCIE_INVENTORY_SIZE) {
|
|
/**
|
|
* @fwts-label FirenzePCIInventoryTooLarge
|
|
* @fwts-advice More PCI inventory than we can send to service
|
|
* processor. The service processor will have an incomplete
|
|
* view of the world.
|
|
*/
|
|
prlog(PR_ERR, "Inventory (%lld bytes) too large\n",
|
|
aend - abase);
|
|
goto bail;
|
|
}
|
|
|
|
/* Map this in the TCEs */
|
|
fsp_tce_map(PSI_DMA_PCIE_INVENTORY, (void *)abase, aend - abase);
|
|
|
|
/* Send FSP message */
|
|
rc = fsp_sync_msg(fsp_mkmsg(FSP_CMD_PCI_POWER_CONF, 3,
|
|
hi32(offset), lo32(offset),
|
|
end - base), true);
|
|
if (rc)
|
|
{
|
|
/**
|
|
* @fwts-label FirenzePCIInventoryError
|
|
* @fwts-advice Error communicating with service processor
|
|
* when sending PCI Inventory.
|
|
*/
|
|
prlog(PR_ERR, "Error %lld sending inventory\n", rc);
|
|
}
|
|
|
|
/* Unmap */
|
|
fsp_tce_unmap(PSI_DMA_PCIE_INVENTORY, aend - abase);
|
|
bail:
|
|
/*
|
|
* We free the inventory. We'll have to redo that on hotplug
|
|
* when we support it but that isn't the case yet
|
|
*/
|
|
free(firenze_inv_data);
|
|
firenze_inv_data = NULL;
|
|
firenze_inv_cnt = 0;
|
|
}
|
|
|
|
/* The function is called when the I2C request is completed
|
|
* successfully, or with errors.
|
|
*/
|
|
static void firenze_i2c_req_done(int rc, struct i2c_request *req)
|
|
{
|
|
struct pci_slot *slot = req->user_data;
|
|
uint32_t state;
|
|
|
|
/* Check if there are errors for the completion */
|
|
if (rc) {
|
|
/**
|
|
* @fwts-label FirenzePCII2CError
|
|
* @fwts-advice On Firenze platforms, I2C is used to control
|
|
* power to PCI slots. Errors here mean we may be in trouble
|
|
* in regards to PCI slot power on/off.
|
|
*/
|
|
prlog(PR_ERR, "Error %d from I2C request on slot %016llx\n",
|
|
rc, slot->id);
|
|
return;
|
|
}
|
|
|
|
/* Check the request type */
|
|
if (req->op != SMBUS_READ && req->op != SMBUS_WRITE) {
|
|
/**
|
|
* @fwts-label FirenzePCII2CInvalid
|
|
* @fwts-advice Likely a coding error: invalid I2C request.
|
|
*/
|
|
prlog(PR_ERR, "Invalid I2C request %d on slot %016llx\n",
|
|
req->op, slot->id);
|
|
return;
|
|
}
|
|
|
|
/* After writting power status to I2C slave, we need at least
|
|
* 5ms delay for the slave to settle down. We also have the
|
|
* delay after reading the power status as well.
|
|
*/
|
|
switch (slot->state) {
|
|
case FIRENZE_PCI_SLOT_FRESET_WAIT_RSP:
|
|
prlog(PR_DEBUG, "%016llx FRESET: I2C request completed\n",
|
|
slot->id);
|
|
state = FIRENZE_PCI_SLOT_FRESET_DELAY;
|
|
break;
|
|
case FIRENZE_PCI_SLOT_SPOWER_START:
|
|
prlog(PR_DEBUG, "%016llx SPOWER: I2C request completed\n",
|
|
slot->id);
|
|
state = FIRENZE_PCI_SLOT_SPOWER_DONE;
|
|
break;
|
|
default:
|
|
/**
|
|
* @fwts-label FirenzePCISlotI2CStateError
|
|
* @fwts-advice The Firenze platform uses I2C to control
|
|
* power to PCI slots. Something went wrong in the state
|
|
* machine controlling that. Slots may/may not have power.
|
|
*/
|
|
prlog(PR_ERR, "Wrong state %08x on slot %016llx\n",
|
|
slot->state, slot->id);
|
|
return;
|
|
}
|
|
|
|
/* Switch to net state */
|
|
pci_slot_set_state(slot, state);
|
|
}
|
|
|
|
/* This function is called to setup normal PCI device or PHB slot.
|
|
* For the later case, the slot doesn't have the associated PCI
|
|
* device. Besides, the I2C response timeout is set to 5s. We might
|
|
* improve I2C in future to support priorized requests so that the
|
|
* timeout can be shortened.
|
|
*/
|
|
static int64_t firenze_pci_slot_freset(struct pci_slot *slot)
|
|
{
|
|
struct firenze_pci_slot *plat_slot = slot->data;
|
|
uint8_t *pval, presence = 1;
|
|
uint32_t timeout;
|
|
|
|
switch (slot->state) {
|
|
case FIRENZE_PCI_SLOT_NORMAL:
|
|
case FIRENZE_PCI_SLOT_FRESET_START:
|
|
prlog(PR_DEBUG, "%016llx FRESET: Starts\n",
|
|
slot->id);
|
|
|
|
/* Bail if nothing is connected */
|
|
if (slot->ops.get_presence_state)
|
|
slot->ops.get_presence_state(slot, &presence);
|
|
if (!presence) {
|
|
prlog(PR_DEBUG, "%016llx FRESET: No device\n",
|
|
slot->id);
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/* Prepare link down */
|
|
if (slot->ops.prepare_link_change) {
|
|
prlog(PR_DEBUG, "%016llx FRESET: Prepares link down\n",
|
|
slot->id);
|
|
slot->ops.prepare_link_change(slot, false);
|
|
}
|
|
|
|
/* Send I2C request */
|
|
prlog(PR_DEBUG, "%016llx FRESET: Check power state\n",
|
|
slot->id);
|
|
plat_slot->next_state =
|
|
FIRENZE_PCI_SLOT_FRESET_POWER_STATE;
|
|
plat_slot->req->op = SMBUS_READ;
|
|
slot->retries = FIRENZE_PCI_SLOT_RETRIES;
|
|
pci_slot_set_state(slot,
|
|
FIRENZE_PCI_SLOT_FRESET_WAIT_RSP);
|
|
if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
|
|
plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT;
|
|
else
|
|
plat_slot->req->timeout = 0ul;
|
|
i2c_queue_req(plat_slot->req);
|
|
return pci_slot_set_sm_timeout(slot,
|
|
msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
|
|
case FIRENZE_PCI_SLOT_FRESET_WAIT_RSP:
|
|
if (slot->retries-- == 0) {
|
|
prlog(PR_DEBUG, "%016llx FRESET: Timeout waiting for %08x\n",
|
|
slot->id, plat_slot->next_state);
|
|
goto out;
|
|
}
|
|
|
|
check_timers(false);
|
|
return pci_slot_set_sm_timeout(slot,
|
|
msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
|
|
case FIRENZE_PCI_SLOT_FRESET_DELAY:
|
|
prlog(PR_DEBUG, "%016llx FRESET: Delay %dms on I2C completion\n",
|
|
slot->id, FIRENZE_PCI_SLOT_DELAY);
|
|
pci_slot_set_state(slot, plat_slot->next_state);
|
|
return pci_slot_set_sm_timeout(slot,
|
|
msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
|
|
case FIRENZE_PCI_SLOT_FRESET_POWER_STATE:
|
|
/* Update last power status */
|
|
pval = (uint8_t *)(plat_slot->req->rw_buf);
|
|
*plat_slot->power_status = *pval;
|
|
|
|
/* Power is on, turn it off */
|
|
if (((*pval) & plat_slot->power_mask) == plat_slot->power_on) {
|
|
prlog(PR_DEBUG, "%016llx FRESET: Power (%02x) on, turn off\n",
|
|
slot->id, *pval);
|
|
(*pval) &= ~plat_slot->power_mask;
|
|
(*pval) |= plat_slot->power_off;
|
|
plat_slot->req->op = SMBUS_WRITE;
|
|
slot->retries = FIRENZE_PCI_SLOT_RETRIES;
|
|
plat_slot->next_state =
|
|
FIRENZE_PCI_SLOT_FRESET_POWER_OFF;
|
|
pci_slot_set_state(slot,
|
|
FIRENZE_PCI_SLOT_FRESET_WAIT_RSP);
|
|
|
|
if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
|
|
timeout = FIRENZE_PCI_I2C_TIMEOUT;
|
|
else
|
|
timeout = 0ul;
|
|
plat_slot->req->timeout = timeout;
|
|
|
|
i2c_queue_req(plat_slot->req);
|
|
return pci_slot_set_sm_timeout(slot,
|
|
msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
|
|
}
|
|
|
|
/* Power is off, turn it on */
|
|
/* Fallthrough */
|
|
case FIRENZE_PCI_SLOT_FRESET_POWER_OFF:
|
|
/* Update last power status */
|
|
pval = (uint8_t *)(plat_slot->req->rw_buf);
|
|
*plat_slot->power_status = *pval;
|
|
|
|
prlog(PR_DEBUG, "%016llx FRESET: Power (%02x) off, turn on\n",
|
|
slot->id, *pval);
|
|
(*pval) &= ~plat_slot->power_mask;
|
|
(*pval) |= plat_slot->power_on;
|
|
plat_slot->req->op = SMBUS_WRITE;
|
|
plat_slot->next_state =
|
|
FIRENZE_PCI_SLOT_FRESET_POWER_ON;
|
|
slot->retries = FIRENZE_PCI_SLOT_RETRIES;
|
|
pci_slot_set_state(slot,
|
|
FIRENZE_PCI_SLOT_FRESET_WAIT_RSP);
|
|
|
|
if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
|
|
plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT;
|
|
else
|
|
plat_slot->req->timeout = 0ul;
|
|
i2c_queue_req(plat_slot->req);
|
|
return pci_slot_set_sm_timeout(slot,
|
|
msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
|
|
case FIRENZE_PCI_SLOT_FRESET_POWER_ON:
|
|
/* Update last power status */
|
|
pval = (uint8_t *)(plat_slot->req->rw_buf);
|
|
*plat_slot->power_status = *pval;
|
|
|
|
pci_slot_set_state(slot, FIRENZE_PCI_SLOT_LINK_START);
|
|
return slot->ops.poll_link(slot);
|
|
default:
|
|
prlog(PR_DEBUG, "%016llx FRESET: Unexpected state %08x\n",
|
|
slot->id, slot->state);
|
|
}
|
|
|
|
out:
|
|
pci_slot_set_state(slot, FIRENZE_PCI_SLOT_NORMAL);
|
|
return OPAL_HARDWARE;
|
|
}
|
|
|
|
static int64_t firenze_pci_slot_perst(struct pci_slot *slot)
|
|
{
|
|
struct firenze_pci_slot *plat_slot = slot->data;
|
|
uint8_t presence = 1;
|
|
uint16_t ctrl;
|
|
|
|
switch (slot->state) {
|
|
case FIRENZE_PCI_SLOT_NORMAL:
|
|
case FIRENZE_PCI_SLOT_FRESET_START:
|
|
prlog(PR_DEBUG, "%016llx PERST: Starts\n",
|
|
slot->id);
|
|
|
|
/* Bail if nothing is connected */
|
|
if (slot->ops.get_presence_state)
|
|
slot->ops.get_presence_state(slot, &presence);
|
|
if (!presence) {
|
|
prlog(PR_DEBUG, "%016llx PERST: No device\n",
|
|
slot->id);
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/* Prepare link down */
|
|
if (slot->ops.prepare_link_change) {
|
|
prlog(PR_DEBUG, "%016llx PERST: Prepare link down\n",
|
|
slot->id);
|
|
slot->ops.prepare_link_change(slot, false);
|
|
}
|
|
|
|
/* Assert PERST */
|
|
prlog(PR_DEBUG, "%016llx PERST: Assert\n",
|
|
slot->id);
|
|
pci_cfg_read16(slot->phb, slot->pd->bdfn,
|
|
plat_slot->perst_reg, &ctrl);
|
|
ctrl |= plat_slot->perst_bit;
|
|
pci_cfg_write16(slot->phb, slot->pd->bdfn,
|
|
plat_slot->perst_reg, ctrl);
|
|
pci_slot_set_state(slot,
|
|
FIRENZE_PCI_SLOT_PERST_DEASSERT);
|
|
return pci_slot_set_sm_timeout(slot, msecs_to_tb(250));
|
|
case FIRENZE_PCI_SLOT_PERST_DEASSERT:
|
|
/* Deassert PERST */
|
|
pci_cfg_read16(slot->phb, slot->pd->bdfn,
|
|
plat_slot->perst_reg, &ctrl);
|
|
ctrl &= ~plat_slot->perst_bit;
|
|
pci_cfg_write16(slot->phb, slot->pd->bdfn,
|
|
plat_slot->perst_reg, ctrl);
|
|
pci_slot_set_state(slot,
|
|
FIRENZE_PCI_SLOT_PERST_DELAY);
|
|
return pci_slot_set_sm_timeout(slot, msecs_to_tb(1500));
|
|
case FIRENZE_PCI_SLOT_PERST_DELAY:
|
|
pci_slot_set_state(slot, FIRENZE_PCI_SLOT_LINK_START);
|
|
return slot->ops.poll_link(slot);
|
|
default:
|
|
prlog(PR_DEBUG, "%016llx PERST: Unexpected state %08x\n",
|
|
slot->id, slot->state);
|
|
}
|
|
|
|
pci_slot_set_state(slot, FIRENZE_PCI_SLOT_NORMAL);
|
|
return OPAL_HARDWARE;
|
|
}
|
|
|
|
static int64_t firenze_pci_slot_get_power_state(struct pci_slot *slot,
|
|
uint8_t *val)
|
|
{
|
|
if (slot->state != FIRENZE_PCI_SLOT_NORMAL)
|
|
{
|
|
/**
|
|
* @fwts-label FirenzePCISlotGPowerState
|
|
* @fwts-advice Unexpected state in the FIRENZE PCI Slot
|
|
* state machine. This could mean PCI is not functioning
|
|
* correctly.
|
|
*/
|
|
prlog(PR_ERR, "%016llx GPOWER: Unexpected state %08x\n",
|
|
slot->id, slot->state);
|
|
}
|
|
|
|
*val = slot->power_state;
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static int64_t firenze_pci_slot_set_power_state(struct pci_slot *slot,
|
|
uint8_t val)
|
|
{
|
|
struct firenze_pci_slot *plat_slot = slot->data;
|
|
uint8_t *pval;
|
|
|
|
if (slot->state != FIRENZE_PCI_SLOT_NORMAL)
|
|
{
|
|
/**
|
|
* @fwts-label FirenzePCISlotSPowerState
|
|
* @fwts-advice Unexpected state in the FIRENZE PCI Slot
|
|
* state machine. This could mean PCI is not functioning
|
|
* correctly.
|
|
*/
|
|
prlog(PR_ERR, "%016llx SPOWER: Unexpected state %08x\n",
|
|
slot->id, slot->state);
|
|
}
|
|
|
|
if (val != PCI_SLOT_POWER_OFF && val != PCI_SLOT_POWER_ON)
|
|
return OPAL_PARAMETER;
|
|
|
|
if (!pci_slot_has_flags(slot, PCI_SLOT_FLAG_ENFORCE) &&
|
|
slot->power_state == val)
|
|
return OPAL_SUCCESS;
|
|
|
|
/* Update with the requested power state and bail immediately when
|
|
* surprise hotplug is supported on the slot. It keeps the power
|
|
* supply to the slot on and it guarentees the link state change
|
|
* events will be raised properly during surprise hot add/remove.
|
|
*/
|
|
if (!pci_slot_has_flags(slot, PCI_SLOT_FLAG_ENFORCE) &&
|
|
slot->surprise_pluggable) {
|
|
slot->power_state = val;
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
slot->power_state = val;
|
|
pci_slot_set_state(slot, FIRENZE_PCI_SLOT_SPOWER_START);
|
|
|
|
plat_slot->req->op = SMBUS_WRITE;
|
|
pval = (uint8_t *)plat_slot->req->rw_buf;
|
|
if (val == PCI_SLOT_POWER_ON) {
|
|
*pval = *plat_slot->power_status;
|
|
(*pval) &= ~plat_slot->power_mask;
|
|
(*pval) |= plat_slot->power_on;
|
|
} else {
|
|
*pval = *plat_slot->power_status;
|
|
(*pval) &= ~plat_slot->power_mask;
|
|
(*pval) |= plat_slot->power_off;
|
|
}
|
|
|
|
if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
|
|
plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT;
|
|
else
|
|
plat_slot->req->timeout = 0ul;
|
|
i2c_queue_req(plat_slot->req);
|
|
|
|
return OPAL_ASYNC_COMPLETION;
|
|
}
|
|
|
|
static struct i2c_bus *firenze_pci_find_i2c_bus(uint8_t chip,
|
|
uint8_t eng,
|
|
uint8_t port)
|
|
{
|
|
struct dt_node *np, *child;
|
|
uint32_t reg;
|
|
|
|
/* Iterate I2C masters */
|
|
dt_for_each_compatible(dt_root, np, "ibm,power8-i2cm") {
|
|
if (!np->parent ||
|
|
!dt_node_is_compatible(np->parent, "ibm,power8-xscom"))
|
|
continue;
|
|
|
|
/* Check chip index */
|
|
reg = dt_prop_get_u32(np->parent, "ibm,chip-id");
|
|
if (reg != chip)
|
|
continue;
|
|
|
|
/* Check I2C master index */
|
|
reg = dt_prop_get_u32(np, "chip-engine#");
|
|
if (reg != eng)
|
|
continue;
|
|
|
|
/* Iterate I2C buses */
|
|
dt_for_each_child(np, child) {
|
|
if (!dt_node_is_compatible(child, "ibm,power8-i2c-port"))
|
|
continue;
|
|
|
|
/* Check I2C port index */
|
|
reg = dt_prop_get_u32(child, "reg");
|
|
if (reg != port)
|
|
continue;
|
|
|
|
reg = dt_prop_get_u32(child, "ibm,opal-id");
|
|
return i2c_find_bus_by_id(reg);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int64_t firenze_pci_slot_fixup_one_reg(struct pci_slot *slot,
|
|
struct firenze_pci_slot_fixup_info *fixup)
|
|
{
|
|
struct firenze_pci_slot *plat_slot = slot->data;
|
|
struct i2c_request req;
|
|
uint8_t buf;
|
|
int64_t rc;
|
|
|
|
/*
|
|
* Fill out our own request structure since we don't want to invoke the
|
|
* normal completion handler.
|
|
*/
|
|
memset(&req, 0, sizeof(req));
|
|
req.dev_addr = plat_slot->req->dev_addr;
|
|
req.bus = plat_slot->req->bus;
|
|
req.offset = fixup->reg;
|
|
req.offset_bytes = 1;
|
|
req.rw_buf = &buf;
|
|
req.rw_len = 1;
|
|
req.timeout = FIRENZE_PCI_I2C_TIMEOUT;
|
|
|
|
req.op = SMBUS_WRITE;
|
|
buf = fixup->val;
|
|
rc = i2c_request_sync(&req);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/*
|
|
* Check the register fixup has been applied. It's not the end of the
|
|
* world we don't, but eh...
|
|
*/
|
|
req.op = SMBUS_READ;
|
|
rc = i2c_request_sync(&req);
|
|
if (rc == OPAL_SUCCESS && buf != fixup->val) {
|
|
prlog(PR_ERR, "Error verifying fixup [%s] - (%02x, %02x, %02x)\n",
|
|
fixup->label, fixup->reg, fixup->val, buf);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void firenze_pci_slot_fixup(struct pci_slot *slot,
|
|
struct firenze_pci_slot_info *info)
|
|
{
|
|
int64_t rc, i, applied = 0;
|
|
const uint32_t *p;
|
|
uint64_t id;
|
|
|
|
p = dt_prop_get_def(dt_root, "ibm,vpd-lx-info", NULL);
|
|
id = p ? (((uint64_t)p[1] << 32) | p[2]) : 0ul;
|
|
if (id != LX_VPD_2S4U_BACKPLANE &&
|
|
id != LX_VPD_1S4U_BACKPLANE)
|
|
return;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(firenze_pci_slot_fixup_tbl); i++) {
|
|
struct firenze_pci_slot_fixup_info *fixup =
|
|
&firenze_pci_slot_fixup_tbl[i];
|
|
|
|
if (strcmp(info->label, fixup->label))
|
|
continue;
|
|
|
|
rc = firenze_pci_slot_fixup_one_reg(slot, fixup);
|
|
if (rc) {
|
|
prlog(PR_ERR, "I2C error (%lld) applying fixup [%s] - (%02x, %02x)\n",
|
|
rc, fixup->label, fixup->reg, fixup->val);
|
|
return;
|
|
}
|
|
|
|
applied++;
|
|
}
|
|
|
|
if (applied)
|
|
prlog(PR_INFO, "Applied %lld fixups for [%s]\n",
|
|
applied, info->label);
|
|
}
|
|
|
|
static void firenze_pci_setup_power_mgt(struct pci_slot *slot,
|
|
struct firenze_pci_slot *plat_slot,
|
|
struct firenze_pci_slot_info *info)
|
|
{
|
|
plat_slot->i2c_bus = firenze_pci_find_i2c_bus(info->chip_id,
|
|
info->master_id,
|
|
info->port_id);
|
|
if (!plat_slot->i2c_bus)
|
|
return;
|
|
|
|
plat_slot->req = zalloc(sizeof(*plat_slot->req));
|
|
if (!plat_slot->req)
|
|
return;
|
|
|
|
plat_slot->req->dev_addr = info->slave_addr;
|
|
plat_slot->req->offset_bytes = 1;
|
|
plat_slot->req->rw_buf = plat_slot->i2c_rw_buf;
|
|
plat_slot->req->rw_len = 1;
|
|
plat_slot->req->completion = firenze_i2c_req_done;
|
|
plat_slot->req->user_data = slot;
|
|
plat_slot->req->bus = plat_slot->i2c_bus;
|
|
|
|
firenze_pci_slot_fixup(slot, info);
|
|
|
|
/*
|
|
* For all slots, the register used to change the power state is
|
|
* always 0x69. It could have been set to something else in the
|
|
* above fixup. Lets fix it to 0x69 here.
|
|
*
|
|
* The power states of two slots are controlled by one register.
|
|
* This means two slots have to share data buffer for power states,
|
|
* which are tracked by struct firenze_pci_slot_info::power_status.
|
|
* With it, we can avoid affecting slot#B's power state when trying
|
|
* to adjust that on slot#A. Also, the initial power states for all
|
|
* slots are assumed to be PCI_SLOT_POWER_ON.
|
|
*/
|
|
plat_slot->req->offset = 0x69;
|
|
plat_slot->power_status = &firenze_pci_slots[info->buddy].power_status;
|
|
switch (info->channel) {
|
|
case 0:
|
|
plat_slot->power_mask = 0x33;
|
|
plat_slot->power_on = 0x22;
|
|
plat_slot->power_off = 0;
|
|
break;
|
|
case 1:
|
|
plat_slot->power_status = &firenze_pci_slots[info->buddy].power_status;
|
|
plat_slot->power_mask = 0xcc;
|
|
plat_slot->power_on = 0x88;
|
|
plat_slot->power_off = 0;
|
|
break;
|
|
default:
|
|
prlog(PR_ERR, "%016llx: Invalid channel %d\n",
|
|
slot->id, info->channel);
|
|
}
|
|
}
|
|
|
|
static void firenze_pci_slot_init(struct pci_slot *slot)
|
|
{
|
|
struct lxvpd_pci_slot *s = slot->data;
|
|
struct firenze_pci_slot *plat_slot = slot->data;
|
|
struct firenze_pci_slot_info *info = NULL;
|
|
uint32_t vdid;
|
|
int i;
|
|
|
|
/* Init the slot info from the LXVPD */
|
|
slot->ops.add_properties = lxvpd_add_slot_properties;
|
|
|
|
/* Search for power control information in the per-system table */
|
|
for (i = 0; i < ARRAY_SIZE(firenze_pci_slots); i++) {
|
|
if (firenze_pci_slots[i].index == s->slot_index &&
|
|
!strcmp(firenze_pci_slots[i].label, s->label)) {
|
|
info = &firenze_pci_slots[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!info)
|
|
return;
|
|
|
|
/* Search I2C bus for external power mgt */
|
|
if (slot->power_ctl)
|
|
firenze_pci_setup_power_mgt(slot, plat_slot, info);
|
|
|
|
/*
|
|
* If the slot has external power logic, to override the
|
|
* default power management methods. Because of the bad
|
|
* I2C design, the API supplied by I2C is really hard to
|
|
* be utilized. To figure out power status retrival or
|
|
* configuration after we have a blocking API for that.
|
|
*/
|
|
if (plat_slot->req) {
|
|
slot->ops.freset = firenze_pci_slot_freset;
|
|
slot->ops.get_power_state = firenze_pci_slot_get_power_state;
|
|
slot->ops.set_power_state = firenze_pci_slot_set_power_state;
|
|
prlog(PR_DEBUG, "%016llx: External power mgt initialized\n",
|
|
slot->id);
|
|
} else if (info->inband_perst) {
|
|
/*
|
|
* For PLX downstream ports, PCI config register can be
|
|
* leveraged to do PERST. If the slot doesn't have external
|
|
* power management stuff, lets try to stick to the PERST
|
|
* logic if applicable
|
|
*/
|
|
if (slot->pd->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
|
|
pci_cfg_read32(slot->phb, slot->pd->bdfn,
|
|
PCI_CFG_VENDOR_ID, &vdid);
|
|
switch (vdid) {
|
|
case 0x873210b5: /* PLX8732 */
|
|
case 0x874810b5: /* PLX8748 */
|
|
plat_slot->perst_reg = 0x80;
|
|
plat_slot->perst_bit = 0x0400;
|
|
slot->ops.freset = firenze_pci_slot_perst;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void firenze_pci_setup_phb(struct phb *phb, unsigned int index)
|
|
{
|
|
uint32_t hub_id;
|
|
|
|
/* Grab Hub ID used to parse VPDs */
|
|
hub_id = dt_prop_get_u32_def(phb->dt_node, "ibm,hub-id", 0);
|
|
|
|
/* Process the pcie slot entries from the lx vpd lid */
|
|
lxvpd_process_slot_entries(phb, dt_root, hub_id,
|
|
index, sizeof(struct firenze_pci_slot));
|
|
}
|
|
|
|
void firenze_pci_get_slot_info(struct phb *phb, struct pci_device *pd)
|
|
{
|
|
struct pci_slot *slot;
|
|
struct lxvpd_pci_slot *s;
|
|
|
|
/* Prepare the PCI inventory */
|
|
firenze_pci_add_inventory(phb, pd);
|
|
|
|
if (pd->dev_type != PCIE_TYPE_ROOT_PORT &&
|
|
pd->dev_type != PCIE_TYPE_SWITCH_UPPORT &&
|
|
pd->dev_type != PCIE_TYPE_SWITCH_DNPORT &&
|
|
pd->dev_type != PCIE_TYPE_PCIE_TO_PCIX)
|
|
return;
|
|
|
|
/* Create PCIe slot */
|
|
slot = pcie_slot_create(phb, pd);
|
|
if (!slot)
|
|
return;
|
|
|
|
/* Root complex inherits methods from PHB slot */
|
|
if (!pd->parent && phb->slot)
|
|
memcpy(&slot->ops, &phb->slot->ops, sizeof(struct pci_slot_ops));
|
|
|
|
/* Patch PCIe slot */
|
|
s = lxvpd_get_slot(slot);
|
|
if (s) {
|
|
lxvpd_extract_info(slot, s);
|
|
firenze_pci_slot_init(slot);
|
|
}
|
|
}
|