blob: 45996342a2ad701c429b91f3572707ee0beb1bf7 [file] [log] [blame]
/* Copyright 2013-2016 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 <opal-msg.h>
#include <pci-cfg.h>
#include <pci.h>
#include <pci-slot.h>
/* Debugging options */
#define PCIE_SLOT_PREFIX "PCIE-SLOT-%016llx "
#define PCIE_SLOT_DBG(s, fmt, a...) \
prlog(PR_DEBUG, PCIE_SLOT_PREFIX fmt, (s)->id, ##a)
static int64_t pcie_slot_get_presence_state(struct pci_slot *slot, uint8_t *val)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint32_t ecap;
uint16_t state;
/* The presence is always on if it's a switch upstream port */
if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT) {
*val = OPAL_PCI_SLOT_PRESENT;
return OPAL_SUCCESS;
}
/*
* The presence is always on if a switch downstream port
* doesn't support slot capability according to PCIE spec.
*/
if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT &&
!(slot->pcie_cap & PCICAP_EXP_CAP_SLOT)) {
*val = OPAL_PCI_SLOT_PRESENT;
return OPAL_SUCCESS;
}
/* Retrieve presence status */
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTSTAT, &state);
if (state & PCICAP_EXP_SLOTSTAT_PDETECTST)
*val = OPAL_PCI_SLOT_PRESENT;
else
*val = OPAL_PCI_SLOT_EMPTY;
return OPAL_SUCCESS;
}
static int64_t pcie_slot_get_link_state(struct pci_slot *slot,
uint8_t *val)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint32_t ecap;
int16_t state;
/*
* The link behind switch upstream port is always on
* since it doesn't have a valid link indicator.
*/
if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT) {
*val = 1;
return OPAL_SUCCESS;
}
/* Retrieve link width */
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_LSTAT, &state);
if (state & PCICAP_EXP_LSTAT_DLLL_ACT)
*val = ((state & PCICAP_EXP_LSTAT_WIDTH) >> 4);
else
*val = 0;
return OPAL_SUCCESS;
}
static int64_t pcie_slot_get_power_state(struct pci_slot *slot __unused,
uint8_t *val)
{
/* We should return the cached power state that is same to
* the PCI slot hotplug state (added/removed). Otherwise,
* the OS will see mismatched states, causing the adapter
* behind the slot can't be probed successfully on request
* of hot add. So we could run into the situation where the
* OS sees power-off but it's on in hardware.
*/
*val = slot->power_state;
return OPAL_SUCCESS;
}
static int64_t pcie_slot_get_attention_state(struct pci_slot *slot,
uint8_t *val)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint32_t ecap;
uint16_t state;
/* Attention is off if the capability is missing */
if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_ATTNI)) {
*val = 0;
return OPAL_SUCCESS;
}
/* Retrieve attention state */
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, &state);
state = (state & PCICAP_EXP_SLOTCTL_ATTNI) >> 6;
switch (state) {
case PCIE_INDIC_ON:
*val = PCI_SLOT_ATTN_LED_ON;
break;
case PCIE_INDIC_BLINK:
*val = PCI_SLOT_ATTN_LED_BLINK;
break;
case PCIE_INDIC_OFF:
default:
*val = PCI_SLOT_ATTN_LED_OFF;
}
return OPAL_SUCCESS;
}
static int64_t pcie_slot_get_latch_state(struct pci_slot *slot,
uint8_t *val)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint32_t ecap;
uint16_t state;
/* Latch is off if MRL sensor doesn't exist */
if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_MRLSENS)) {
*val = 0;
return OPAL_SUCCESS;
}
/* Retrieve MRL sensor state */
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTSTAT, &state);
if (state & PCICAP_EXP_SLOTSTAT_MRLSENSST)
*val = 1;
else
*val = 0;
return OPAL_SUCCESS;
}
static int64_t pcie_slot_set_attention_state(struct pci_slot *slot,
uint8_t val)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint32_t ecap;
uint16_t state;
/* Drop the request if functionality doesn't exist */
if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_ATTNI))
return OPAL_SUCCESS;
/* Update with the requested state */
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, &state);
state &= ~PCICAP_EXP_SLOTCTL_ATTNI;
switch (val) {
case PCI_SLOT_ATTN_LED_ON:
state |= (PCIE_INDIC_ON << 6);
break;
case PCI_SLOT_ATTN_LED_BLINK:
state |= (PCIE_INDIC_BLINK << 6);
break;
case PCI_SLOT_ATTN_LED_OFF:
state |= (PCIE_INDIC_OFF << 6);
break;
default:
prlog(PR_ERR, PCIE_SLOT_PREFIX
"Invalid attention state (0x%x)\n", slot->id, val);
return OPAL_PARAMETER;
}
pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, state);
return OPAL_SUCCESS;
}
static int64_t pcie_slot_set_power_state_ext(struct pci_slot *slot, uint8_t val,
bool surprise_check)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint32_t ecap;
uint16_t state;
if (slot->power_state == val)
return OPAL_SUCCESS;
/* Update the power state and return immediately if the power
* control functionality isn't supported on the PCI slot.
*/
if (!(slot->slot_cap & PCICAP_EXP_SLOTCAP_PWCTRL)) {
slot->power_state = val;
return OPAL_SUCCESS;
}
/* The power supply to the slot should be always on when surprise
* hotplug is claimed. For this case, update with the requested
* power state and bail immediately.
*
* The PCIe link is likely down if we're powering on the slot upon
* the detected presence. Nothing behind the slot will be probed if
* we do it immediately even we do have PCI devices connected to the
* slot. For this case, we force upper layer to wait for the PCIe
* link to be up before probing the PCI devices behind the slot. It's
* only concerned in surprise hotplug path. In managed hot-add path,
* the PCIe link should have been ready before we power on the slot.
* However, it's not harmful to do so in managed hot-add path.
*
* When flag PCI_SLOT_FLAG_FORCE_POWERON is set for the PCI slot, we
* should turn on the slot's power supply on hardware on user's request
* because that might have been lost. Otherwise, the PCIe link behind
* the slot won't become ready for ever and PCI adapter behind the slot
* can't be probed successfully.
*/
if (surprise_check && slot->surprise_pluggable) {
slot->power_state = val;
if (val == PCI_SLOT_POWER_OFF)
return OPAL_SUCCESS;
if (!pci_slot_has_flags(slot, PCI_SLOT_FLAG_FORCE_POWERON)) {
pci_slot_set_state(slot, PCI_SLOT_STATE_SPOWER_DONE);
return OPAL_ASYNC_COMPLETION;
}
}
pci_slot_set_state(slot, PCI_SLOT_STATE_SPOWER_START);
slot->power_state = val;
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, &state);
state &= ~(PCICAP_EXP_SLOTCTL_PWRCTLR | PCICAP_EXP_SLOTCTL_PWRI);
switch (val) {
case PCI_SLOT_POWER_OFF:
state |= (PCICAP_EXP_SLOTCTL_PWRCTLR | (PCIE_INDIC_OFF << 8));
break;
case PCI_SLOT_POWER_ON:
state |= (PCIE_INDIC_ON << 8);
break;
default:
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
prlog(PR_ERR, PCIE_SLOT_PREFIX
"Invalid power state (0x%x)\n", slot->id, val);
return OPAL_PARAMETER;
}
pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL, state);
pci_slot_set_state(slot, PCI_SLOT_STATE_SPOWER_DONE);
return OPAL_ASYNC_COMPLETION;
}
static int64_t pcie_slot_set_power_state(struct pci_slot *slot, uint8_t val)
{
return pcie_slot_set_power_state_ext(slot, val, true);
}
static int64_t pcie_slot_sm_poll_link(struct pci_slot *slot)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint32_t ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
uint16_t val;
uint8_t presence = 0;
switch (slot->state) {
case PCI_SLOT_STATE_LINK_START_POLL:
PCIE_SLOT_DBG(slot, "LINK: Start polling\n");
/* Link is down for ever without devices attached */
if (slot->ops.get_presence_state)
slot->ops.get_presence_state(slot, &presence);
if (!presence) {
PCIE_SLOT_DBG(slot, "LINK: No adapter, end polling\n");
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
return OPAL_SUCCESS;
}
/* Enable the link without check */
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_LCTL, &val);
val &= ~PCICAP_EXP_LCTL_LINK_DIS;
pci_cfg_write16(phb, pd->bdfn, ecap + PCICAP_EXP_LCTL, val);
/*
* If the link change report isn't supported, we expect
* the link is up and stabilized after one second.
*/
if (!(slot->link_cap & PCICAP_EXP_LCAP_DL_ACT_REP)) {
pci_slot_set_state(slot,
PCI_SLOT_STATE_LINK_DELAY_FINALIZED);
return pci_slot_set_sm_timeout(slot, secs_to_tb(1));
}
/*
* Poll the link state if link state change report is
* supported on the link.
*/
pci_slot_set_state(slot, PCI_SLOT_STATE_LINK_POLLING);
slot->retries = 250;
return pci_slot_set_sm_timeout(slot, msecs_to_tb(20));
case PCI_SLOT_STATE_LINK_DELAY_FINALIZED:
PCIE_SLOT_DBG(slot, "LINK: No link report, end polling\n");
if (slot->ops.prepare_link_change)
slot->ops.prepare_link_change(slot, true);
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
return OPAL_SUCCESS;
case PCI_SLOT_STATE_LINK_POLLING:
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_LSTAT, &val);
if (val & PCICAP_EXP_LSTAT_DLLL_ACT) {
PCIE_SLOT_DBG(slot, "LINK: Link is up, end polling\n");
if (slot->ops.prepare_link_change)
slot->ops.prepare_link_change(slot, true);
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
return OPAL_SUCCESS;
}
/* Check link state again until timeout */
if (slot->retries-- == 0) {
prlog(PR_ERR, PCIE_SLOT_PREFIX
"LINK: Timeout waiting for up (%04x)\n",
slot->id, val);
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
return OPAL_SUCCESS;
}
return pci_slot_set_sm_timeout(slot, msecs_to_tb(20));
default:
prlog(PR_ERR, PCIE_SLOT_PREFIX
"Link: Unexpected slot state %08x\n",
slot->id, slot->state);
}
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
return OPAL_HARDWARE;
}
static void pcie_slot_reset(struct pci_slot *slot, bool assert)
{
struct phb *phb = slot->phb;
struct pci_device *pd = slot->pd;
uint16_t ctl;
pci_cfg_read16(phb, pd->bdfn, PCI_CFG_BRCTL, &ctl);
if (assert)
ctl |= PCI_CFG_BRCTL_SECONDARY_RESET;
else
ctl &= ~PCI_CFG_BRCTL_SECONDARY_RESET;
pci_cfg_write16(phb, pd->bdfn, PCI_CFG_BRCTL, ctl);
}
static int64_t pcie_slot_sm_hreset(struct pci_slot *slot)
{
switch (slot->state) {
case PCI_SLOT_STATE_NORMAL:
PCIE_SLOT_DBG(slot, "HRESET: Starts\n");
if (slot->ops.prepare_link_change) {
PCIE_SLOT_DBG(slot, "HRESET: Prepare for link down\n");
slot->ops.prepare_link_change(slot, false);
}
/* fall through */
case PCI_SLOT_STATE_HRESET_START:
PCIE_SLOT_DBG(slot, "HRESET: Assert\n");
pcie_slot_reset(slot, true);
pci_slot_set_state(slot, PCI_SLOT_STATE_HRESET_HOLD);
return pci_slot_set_sm_timeout(slot, msecs_to_tb(250));
case PCI_SLOT_STATE_HRESET_HOLD:
PCIE_SLOT_DBG(slot, "HRESET: Deassert\n");
pcie_slot_reset(slot, false);
pci_slot_set_state(slot, PCI_SLOT_STATE_LINK_START_POLL);
return pci_slot_set_sm_timeout(slot, msecs_to_tb(1800));
default:
PCIE_SLOT_DBG(slot, "HRESET: Unexpected slot state %08x\n",
slot->state);
}
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
return OPAL_HARDWARE;
}
/*
* Usually, individual platforms need to override the power
* management methods for fundamental reset, but the hot
* reset method is commonly shared.
*/
static int64_t pcie_slot_sm_freset(struct pci_slot *slot)
{
uint8_t power_state = PCI_SLOT_POWER_ON;
switch (slot->state) {
case PCI_SLOT_STATE_NORMAL:
PCIE_SLOT_DBG(slot, "FRESET: Starts\n");
if (slot->ops.prepare_link_change)
slot->ops.prepare_link_change(slot, false);
/* Retrieve power state */
if (slot->ops.get_power_state) {
PCIE_SLOT_DBG(slot, "FRESET: Retrieve power state\n");
slot->ops.get_power_state(slot, &power_state);
}
/* In power on state, power it off */
if (power_state == PCI_SLOT_POWER_ON) {
PCIE_SLOT_DBG(slot, "FRESET: Power is on, turn off\n");
pcie_slot_set_power_state_ext(slot,
PCI_SLOT_POWER_OFF, false);
pci_slot_set_state(slot,
PCI_SLOT_STATE_FRESET_POWER_OFF);
return pci_slot_set_sm_timeout(slot, msecs_to_tb(50));
}
/* No power state change, */
/* fallthrough */
case PCI_SLOT_STATE_FRESET_POWER_OFF:
PCIE_SLOT_DBG(slot, "FRESET: Power is off, turn on\n");
pcie_slot_set_power_state_ext(slot, PCI_SLOT_POWER_ON, false);
pci_slot_set_state(slot, PCI_SLOT_STATE_LINK_START_POLL);
return pci_slot_set_sm_timeout(slot, msecs_to_tb(50));
default:
prlog(PR_ERR, PCIE_SLOT_PREFIX
"FRESET: Unexpected slot state %08x\n",
slot->id, slot->state);
}
pci_slot_set_state(slot, PCI_SLOT_STATE_NORMAL);
return OPAL_HARDWARE;
}
struct pci_slot *pcie_slot_create(struct phb *phb, struct pci_device *pd)
{
struct pci_slot *slot;
uint32_t ecap;
uint16_t slot_ctl;
/* Allocate PCI slot */
slot = pci_slot_alloc(phb, pd);
if (!slot)
return NULL;
/* Cache the link and slot capabilities */
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_CAPABILITY_REG,
&slot->pcie_cap);
pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_LCAP,
&slot->link_cap);
/* Leave PCI slot capability blank if PCI slot isn't supported */
if (slot->pcie_cap & PCICAP_EXP_CAP_SLOT)
pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCAP,
&slot->slot_cap);
else
slot->slot_cap = 0;
if (slot->slot_cap & PCICAP_EXP_SLOTCAP_HPLUG_CAP)
slot->pluggable = 1;
if (slot->slot_cap & PCICAP_EXP_SLOTCAP_PWCTRL) {
slot->power_ctl = 1;
/* The power is on by default */
slot->power_state = PCI_SLOT_POWER_ON;
pci_cfg_read16(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCTL,
&slot_ctl);
if (((slot_ctl & PCICAP_EXP_SLOTCTL_PWRI) >> 8) == PCIE_INDIC_OFF)
slot->power_state = PCI_SLOT_POWER_OFF;
}
if (slot->slot_cap & PCICAP_EXP_SLOTCAP_PWRI)
slot->power_led_ctl = PCI_SLOT_PWR_LED_CTL_KERNEL;
if (slot->slot_cap & PCICAP_EXP_SLOTCAP_ATTNI)
slot->attn_led_ctl = PCI_SLOT_ATTN_LED_CTL_KERNEL;
slot->wired_lanes = ((slot->link_cap & PCICAP_EXP_LCAP_MAXWDTH) >> 4);
/* The surprise hotplug capability is claimed when it's supported
* in the slot's capability bits or link state change reporting is
* supported in PCIe link capability. It means the surprise hotplug
* relies on presence or link state change events. In order for the
* link state change event to be properly raised during surprise hot
* add/remove, the power supply to the slot should be always on.
*
* For PCI slots that don't claim surprise hotplug capability explicitly.
* Its PDC (Presence Detection Change) isn't reliable. To mark that as
* broken on them.
*/
if (slot->pcie_cap & PCICAP_EXP_CAP_SLOT) {
if (slot->slot_cap & PCICAP_EXP_SLOTCAP_HPLUG_SURP) {
slot->surprise_pluggable = 1;
} else if (slot->link_cap & PCICAP_EXP_LCAP_DL_ACT_REP) {
slot->surprise_pluggable = 1;
pci_slot_add_flags(slot, PCI_SLOT_FLAG_BROKEN_PDC);
}
}
/* Standard slot operations */
slot->ops.get_presence_state = pcie_slot_get_presence_state;
slot->ops.get_link_state = pcie_slot_get_link_state;
slot->ops.get_power_state = pcie_slot_get_power_state;
slot->ops.get_attention_state = pcie_slot_get_attention_state;
slot->ops.get_latch_state = pcie_slot_get_latch_state;
slot->ops.set_power_state = pcie_slot_set_power_state;
slot->ops.set_attention_state = pcie_slot_set_attention_state;
/*
* State machine (SM) based reset stuff. The poll function is always
* unified for all cases.
*/
slot->ops.poll_link = pcie_slot_sm_poll_link;
slot->ops.hreset = pcie_slot_sm_hreset;
slot->ops.freset = pcie_slot_sm_freset;
slot->wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN;
slot->connector_type = PCI_SLOT_CONNECTOR_PCIE_NS;
slot->card_desc = PCI_SLOT_DESC_NON_STANDARD;
slot->card_mech = PCI_SLOT_MECH_NONE;
slot->power_led_ctl = PCI_SLOT_PWR_LED_CTL_NONE;
slot->attn_led_ctl = PCI_SLOT_ATTN_LED_CTL_NONE;
return slot;
}
/* FIXME: this is kind of insane */
struct pci_slot *pcie_slot_create_dynamic(struct phb *phb,
struct pci_device *pd)
{
uint32_t ecap, val;
struct pci_slot *slot;
if (!phb || !pd || pd->slot)
return NULL;
/* Try to create slot whose details aren't provided by platform.
* We only care the downstream ports of PCIe switch that connects
* to root port.
*/
if (pd->dev_type != PCIE_TYPE_SWITCH_DNPORT ||
!pd->parent || !pd->parent->parent ||
pd->parent->parent->parent)
return NULL;
ecap = pci_cap(pd, PCI_CFG_CAP_ID_EXP, false);
pci_cfg_read32(phb, pd->bdfn, ecap + PCICAP_EXP_SLOTCAP, &val);
if (!(val & PCICAP_EXP_SLOTCAP_HPLUG_CAP))
return NULL;
slot = pcie_slot_create(phb, pd);
/* On superMicro's "p8dnu" platform, we create dynamic PCI slots
* for all downstream ports of PEX9733 that is connected to PHB
* direct slot. The power supply to the PCI slot is lost after
* PCI adapter is removed from it. The power supply can't be
* turned on when the slot is in empty state. The power supply
* isn't turned on automatically when inserting PCI adapter to
* the slot at later point. We set a flag to the slot here, to
* turn on the power supply in (suprise or managed) hot-add path.
*
* We have same issue with PEX8718 as above on "p8dnu" platform.
*/
if (dt_node_is_compatible(dt_root, "supermicro,p8dnu") && slot &&
slot->pd && (slot->pd->vdid == 0x973310b5 ||
slot->pd->vdid == 0x871810b5))
pci_slot_add_flags(slot, PCI_SLOT_FLAG_FORCE_POWERON);
return slot;
}